├── 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 │ ├── init │ │ ├── mod.rs │ │ ├── init_args.rs │ │ ├── console_helpers.rs │ │ └── dist_profile.rs │ ├── backend │ │ ├── installer │ │ │ ├── powershell.rs │ │ │ ├── shell.rs │ │ │ └── macpkg.rs │ │ └── mod.rs │ ├── config │ │ ├── v1 │ │ │ ├── hosts │ │ │ │ ├── axodotdev.rs │ │ │ │ └── github.rs │ │ │ ├── publishers │ │ │ │ ├── npm.rs │ │ │ │ ├── homebrew.rs │ │ │ │ └── mod.rs │ │ │ ├── builds │ │ │ │ └── generic.rs │ │ │ ├── installers │ │ │ │ ├── msi.rs │ │ │ │ ├── shell.rs │ │ │ │ ├── powershell.rs │ │ │ │ ├── homebrew.rs │ │ │ │ ├── npm.rs │ │ │ │ └── pkg.rs │ │ │ ├── artifacts │ │ │ │ ├── mod.rs │ │ │ │ └── archives.rs │ │ │ └── loader.rs │ │ └── version.rs │ ├── net.rs │ ├── migrate │ │ └── from_v0.rs │ ├── sign │ │ └── mod.rs │ ├── build │ │ └── fake.rs │ └── platform │ │ └── github_runners.rs ├── tests │ ├── gallery │ │ ├── errors.rs │ │ ├── mod.rs │ │ └── dist │ │ │ └── tools.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 │ │ ├── workflow-artifacts.png │ │ ├── simple-app-manifest.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 │ ├── workspaces │ │ └── index.md │ ├── supplychain-security │ │ ├── attestations │ │ │ └── github.md │ │ └── index.md │ ├── SUMMARY.md │ ├── updating.md │ ├── install.md │ └── custom-builds.md └── book.toml ├── rust-toolchain.toml ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ ├── publish-crates.yml │ └── web.yml ├── typos.toml ├── .gitattributes ├── SECURITY.md ├── cargo-dist-schema ├── Cargo.toml └── README.md ├── CODE_OF_CONDUCT.md ├── LICENSE-MIT ├── dist-workspace.toml ├── CONTRIBUTING.md ├── 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/mistydemeo/cargo-dist/main/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/mistydemeo/cargo-dist/main/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/mistydemeo/cargo-dist/main/book/src/img/global-build.png -------------------------------------------------------------------------------- /book/src/img/signing-totp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistydemeo/cargo-dist/main/book/src/img/signing-totp.png -------------------------------------------------------------------------------- /book/src/img/simple-oranda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistydemeo/cargo-dist/main/book/src/img/simple-oranda.png -------------------------------------------------------------------------------- /book/src/img/workspace-log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistydemeo/cargo-dist/main/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/mistydemeo/cargo-dist/main/book/src/img/quickstart-build.png -------------------------------------------------------------------------------- /book/src/img/quickstart-plan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistydemeo/cargo-dist/main/book/src/img/quickstart-plan.png -------------------------------------------------------------------------------- /book/src/img/signing-cred-id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistydemeo/cargo-dist/main/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/mistydemeo/cargo-dist/main/book/src/img/announcement-error.png -------------------------------------------------------------------------------- /book/src/img/human-manifest-all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistydemeo/cargo-dist/main/book/src/img/human-manifest-all.png -------------------------------------------------------------------------------- /book/src/img/signing-properties.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistydemeo/cargo-dist/main/book/src/img/signing-properties.png -------------------------------------------------------------------------------- /book/src/img/workflow-artifacts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistydemeo/cargo-dist/main/book/src/img/workflow-artifacts.png -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.79" 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-app-manifest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistydemeo/cargo-dist/main/book/src/img/simple-app-manifest.png -------------------------------------------------------------------------------- /book/src/img/simple-github-release.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistydemeo/cargo-dist/main/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/mistydemeo/cargo-dist/main/book/src/img/simple-app-manifest-with-files.png -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-create-react-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistydemeo/cargo-dist/main/axoproject/tests/projects/npm-create-react-app/public/favicon.ico -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-create-react-app/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistydemeo/cargo-dist/main/axoproject/tests/projects/npm-create-react-app/public/logo192.png -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-create-react-app/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistydemeo/cargo-dist/main/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 | -------------------------------------------------------------------------------- /cargo-dist/src/init/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod v0; 2 | pub use v0::do_init; 3 | pub mod console_helpers; 4 | mod dist_profile; 5 | mod init_args; 6 | 7 | pub use dist_profile::init_dist_profile; 8 | pub use init_args::InitArgs; 9 | -------------------------------------------------------------------------------- /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 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/init/init_args.rs: -------------------------------------------------------------------------------- 1 | use crate::config::HostingStyle; 2 | use camino::Utf8PathBuf; 3 | 4 | /// Arguments for `dist init` ([`do_init`][crate::init::do_init]) 5 | #[derive(Debug)] 6 | pub struct InitArgs { 7 | /// Whether to auto-accept the default values for interactive prompts 8 | pub yes: bool, 9 | /// Don't automatically generate ci 10 | pub no_generate: bool, 11 | /// A path to a json file containing values to set in workspace.metadata.dist 12 | pub with_json_config: Option, 13 | /// Hosts to enable 14 | pub host: Vec, 15 | } 16 | -------------------------------------------------------------------------------- /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/axodotdev/cargo-dist" 10 | edit-url-template = "https://github.com/axodotdev/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 = "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/axodotdev/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.5.0" 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/axodotdev/axoproject/workflows/Rust%20CI/badge.svg?branch=main)](https://github.com/axodotdev/axoproject/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 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | The repositories within the @axodotdev GitHub organization are formally owned 4 | by Axo Developer Co. which is a for-profit C-Corporation incorporated in 5 | Delaware, USA and operated by its global, distributed, team of employees. 6 | 7 | To the extent it is possible, reasonable, and legal, external contributors and 8 | employees are held to the same behavior standards, as defined by the 9 | [Contributor Covenant]'s Pledge and Standards. 10 | 11 | Enforcement will necessarily be different depending on the employment status 12 | of involved parties. 13 | 14 | All decisions are made by [Ashley Williams](mailto:ashley@axo.dev) through a 15 | consensus-seeking process with the [Axo team](https://www.axo.dev/team) and 16 | to the extent it is possible, reasonable, and legal, any involved external 17 | participants. 18 | 19 | [Contributor Covenant]: https://www.contributor-covenant.org/version/2/1/code_of_conduct/ 20 | -------------------------------------------------------------------------------- /cargo-dist/tests/snapshots/error_manifest.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: cargo-dist/tests/cli-tests.rs 3 | expression: format_outputs(&output) 4 | snapshot_kind: text 5 | --- 6 | stdout: 7 | {"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: dist\n\nyou can also request any single package with --tag=dist-v1.0.0-FAKEVERSION\n","labels": [],"related": []}} 8 | 9 | stderr: 10 | × This workspace doesn't have anything for dist to Release! 11 | help: You may need to pass the current version as --tag, or need to give all your packages the same version 12 | 13 | Here are some options: 14 | 15 | --tag=v1.0.0-FAKEVERSION will Announce: dist 16 | 17 | you can also request any single package with --tag=dist-v1.0.0-FAKEVERSION 18 | -------------------------------------------------------------------------------- /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 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 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022-2024 Axo Developer Co. 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /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/download-artifact@v4 16 | with: 17 | pattern: artifacts-* 18 | path: npm/ 19 | merge-multiple: true 20 | - uses: actions/setup-node@v4 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.2.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 | -------------------------------------------------------------------------------- /.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 dist-schema --token ${CRATES_TOKEN} 35 | env: 36 | CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} 37 | - run: cargo publish -p 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 | -------------------------------------------------------------------------------- /cargo-dist/src/init/console_helpers.rs: -------------------------------------------------------------------------------- 1 | pub(crate) fn checkmark() -> console::StyledObject { 2 | console::style("✔".to_string()).for_stderr().green() 3 | } 4 | 5 | pub(crate) fn notice() -> console::StyledObject { 6 | console::style("⚠️".to_string()).for_stderr().yellow() 7 | } 8 | 9 | pub(crate) fn ctrlc_handler() -> tokio::task::JoinHandle<()> { 10 | // on ctrl-c, dialoguer/console will clean up the rest of its 11 | // formatting, but the cursor will remain hidden unless we 12 | // explicitly go in and show it again 13 | // See: https://github.com/console-rs/dialoguer/issues/294 14 | tokio::spawn(async move { 15 | tokio::signal::ctrl_c().await.unwrap(); 16 | 17 | let term = console::Term::stdout(); 18 | // Ignore the error here if there is any, this is best effort 19 | let _ = term.show_cursor(); 20 | 21 | // Immediately re-exit the process with the same 22 | // exit code the unhandled ctrl-c would have used 23 | let exitstatus = if cfg!(windows) { 24 | 0xc000013a_u32 as i32 25 | } else { 26 | 130 27 | }; 28 | std::process::exit(exitstatus); 29 | }) 30 | } 31 | 32 | pub(crate) fn theme() -> dialoguer::theme::ColorfulTheme { 33 | dialoguer::theme::ColorfulTheme { 34 | checked_item_prefix: console::style(" [x]".to_string()).for_stderr().green(), 35 | unchecked_item_prefix: console::style(" [ ]".to_string()).for_stderr().dim(), 36 | active_item_style: console::Style::new().for_stderr().cyan().bold(), 37 | ..dialoguer::theme::ColorfulTheme::default() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.27.1" 8 | # CI backends to support 9 | ci = "github" 10 | # The installers to generate for each app 11 | installers = ["shell", "powershell", "npm", "homebrew"] 12 | # A GitHub repo to push Homebrew formulas to 13 | tap = "axodotdev/homebrew-tap" 14 | # A namespace to use when publishing this package to the npm registry 15 | npm-scope = "@axodotdev" 16 | # Publish jobs to run in CI 17 | publish-jobs = ["homebrew", "npm", "./publish-crates"] 18 | # Target platforms to build apps for (Rust target-triple syntax) 19 | 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"] 20 | # Which actions to run on pull requests 21 | pr-run-mode = "plan" 22 | # Where to host releases 23 | hosting = ["axodotdev", "github"] 24 | # Whether to install an updater program 25 | install-updater = false 26 | # Whether to enable GitHub Attestations 27 | github-attestations = true 28 | # Path that installers should place binaries in 29 | install-path = "CARGO_HOME" 30 | 31 | [[dist.extra-artifacts]] 32 | artifacts = ["dist-manifest-schema.json"] 33 | build = ["cargo", "run", "--release", "--", "dist", "manifest-schema", "--output=dist-manifest-schema.json"] 34 | 35 | [dist.bin-aliases] 36 | "dist" = ["cargo-dist"] 37 | 38 | [dist.github-custom-runners.aarch64-unknown-linux-gnu.container] 39 | image = "quay.io/pypa/manylinux_2_28_x86_64" 40 | host = "x86_64-unknown-linux-musl" 41 | 42 | [dist.github-custom-runners.aarch64-unknown-linux-musl.container] 43 | image = "quay.io/pypa/manylinux_2_28_x86_64" 44 | host = "x86_64-unknown-linux-musl" 45 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks so much for your interest in contributing to `cargo-dist`. We are excited 4 | about building a community of contributors to the project. Here's some 5 | guiding principles for working with us: 6 | 7 | **1. File an issue first!** 8 | 9 | Except for the absolute tiniest of PRs (e.g. a single typo fix), please file an 10 | issue before opening a PR. This can help ensure that the problem you are trying 11 | to solve and the solution you have in mind will be accepted. Where possible, we 12 | don't want folks wasting time on directions we don't want to take the project. 13 | 14 | **2. Write tests, or at least detailed reproduction steps** 15 | 16 | If you find a bug, the best way to prioritize getting it fixed is to open a PR 17 | with a failing test! If you are opening a bug fix PR, please add a test to show 18 | that your fix works. 19 | 20 | **3. Overcommunicate** 21 | 22 | In all scenarios, please provide as much context as possible- you may not think 23 | it's important but it may be! 24 | 25 | **4. Patience** 26 | 27 | Axo is a very small company, it's possible that we may not be able to 28 | immediately prioritize your issue. We are excite to develop a community of 29 | contributors around this project, but it won't always be on the top of our to-do 30 | list, even if we wish it could be. 31 | 32 | If you haven't heard from us in a while and want to check in, feel free to 33 | at-mention @ashleygwilliams- but please be kind while doing so! 34 | 35 | ## Running tests 36 | 37 | The test suite can be run with `cargo test`. [insta](https://insta.rs/docs/cli/) 38 | is used for snapshots; after running the test suite, use `cargo insta review` to 39 | review any changed snapshots. 40 | 41 | The test suite cannot be run in parallel. Using an alternative test runner like 42 | `nextest` will cause unexpected behavior as tests will override each other's 43 | state. 44 | -------------------------------------------------------------------------------- /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/download-artifact@v4 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/src/migrate/from_v0.rs: -------------------------------------------------------------------------------- 1 | use crate::config::v0::V0DistConfig; 2 | use crate::config::v1::DistConfig; 3 | use crate::{config, errors::DistResult}; 4 | use axoasset::toml; 5 | 6 | // A purely-functional subset of `do_migrate_from_v0()`: 7 | // takes a DistMetadata (v0) and returns an equivalent DistConfig (v1) 8 | // without touching files on disk. 9 | // 10 | // This could theoretically have unit tests written for it, but we would 11 | // need to make every piece of DistConfig derive/impl PartialEq, which is 12 | // a lot. -duckinator 13 | fn migrate_from_v0_dist_config(old_config: V0DistConfig) -> DistConfig { 14 | let V0DistConfig { 15 | dist, 16 | workspace, 17 | package, 18 | } = old_config; 19 | 20 | let dist = dist.map(|dist| { 21 | dist.to_toml_layer(true) 22 | .with_current_config_version() 23 | .with_current_dist_version() 24 | }); 25 | 26 | config::v1::DistConfig { 27 | dist, 28 | workspace, 29 | package, 30 | } 31 | } 32 | 33 | pub fn do_migrate_from_v0() -> DistResult<()> { 34 | let workspaces = config::get_project()?; 35 | let root_workspace = workspaces.root_workspace(); 36 | let manifest_path = &root_workspace.manifest_path; 37 | 38 | if config::v1::load(manifest_path).is_ok() { 39 | // We're already on a V1 config, no need to migrate! 40 | return Ok(()); 41 | } 42 | 43 | eprintln!("migrating dist config from V0 to V1 format..."); 44 | 45 | // Load in the root workspace toml to edit and write back 46 | let Ok(old_config) = config::v0::load(manifest_path) else { 47 | // We don't have a valid v0 _or_ v1 config. No migration can be done. 48 | // It feels weird to return Ok(()) here, but I think it's right? 49 | return Ok(()); 50 | }; 51 | 52 | let config = migrate_from_v0_dist_config(old_config); 53 | 54 | let workspace_toml_text = toml::to_string(&config)?; 55 | 56 | // Write new config file. 57 | axoasset::LocalAsset::write_new(&workspace_toml_text, manifest_path)?; 58 | 59 | Ok(()) 60 | } 61 | -------------------------------------------------------------------------------- /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 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/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/checkout@v4 17 | with: 18 | repository: {{{ tap }}} 19 | token: ${{ secrets.HOMEBREW_TAP_TOKEN }} 20 | # So we have access to the formula 21 | - name: Fetch homebrew formulae 22 | uses: actions/download-artifact@v4 23 | with: 24 | pattern: artifacts-* 25 | path: Formula/ 26 | merge-multiple: true 27 | # This is extra complex because you can make your Formula name not match your app name 28 | # so we need to find releases with a *.rb file, and publish with that filename. 29 | - name: Commit formula files 30 | run: | 31 | git config --global user.name "${GITHUB_USER}" 32 | git config --global user.email "${GITHUB_EMAIL}" 33 | 34 | for release in $(echo "$PLAN" | jq --compact-output '.releases[] | select([.artifacts[] | endswith(".rb")] | any)'); do 35 | filename=$(echo "$release" | jq '.artifacts[] | select(endswith(".rb"))' --raw-output) 36 | name=$(echo "$filename" | sed "s/\.rb$//") 37 | version=$(echo "$release" | jq .app_version --raw-output) 38 | 39 | export PATH="/home/linuxbrew/.linuxbrew/bin:$PATH" 40 | brew update 41 | # We avoid reformatting user-provided data such as the app description and homepage. 42 | brew style --except-cops FormulaAudit/Homepage,FormulaAudit/Desc,FormulaAuditStrict --fix "Formula/${filename}" || true 43 | 44 | git add "Formula/${filename}" 45 | git commit -m "${name} ${version}" 46 | done 47 | git push 48 | -------------------------------------------------------------------------------- /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 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/src/init/dist_profile.rs: -------------------------------------------------------------------------------- 1 | use crate::{config::Config, errors::DistResult, PROFILE_DIST}; 2 | use axoasset::toml_edit; 3 | 4 | pub fn init_dist_profile( 5 | _cfg: &Config, 6 | workspace_toml: &mut toml_edit::DocumentMut, 7 | ) -> DistResult { 8 | let profiles = workspace_toml["profile"].or_insert(toml_edit::table()); 9 | if let Some(t) = profiles.as_table_mut() { 10 | t.set_implicit(true) 11 | } 12 | let dist_profile = &mut profiles[PROFILE_DIST]; 13 | if !dist_profile.is_none() { 14 | return Ok(false); 15 | } 16 | let mut new_profile = toml_edit::table(); 17 | { 18 | // For some detailed discussion, see: https://github.com/axodotdev/cargo-dist/issues/118 19 | let new_profile = new_profile.as_table_mut().unwrap(); 20 | // We're building for release, so this is a good base! 21 | new_profile.insert("inherits", toml_edit::value("release")); 22 | // We're building for SUPER DUPER release, so lto is a good idea to enable! 23 | // 24 | // There's a decent argument for lto=true (aka "fat") here but the cost-benefit 25 | // is a bit complex. Fat LTO can be way more expensive to compute (to the extent 26 | // that enormous applications like chromium can become unbuildable), but definitely 27 | // eeks out a bit more from your binaries. 28 | // 29 | // In principle dist is targeting True Shippable Binaries and so it's 30 | // worth it to go nuts getting every last drop out of your binaries... but a lot 31 | // of people are going to build binaries that might never even be used, so really 32 | // we're just burning a bunch of CI time for nothing. 33 | // 34 | // The user has the freedom to crank this up higher (and/or set codegen-units=1) 35 | // if they think it's worth it, but we otherwise probably shouldn't set the planet 36 | // on fire just because Number Theoretically Go Up. 37 | new_profile.insert("lto", toml_edit::value("thin")); 38 | new_profile 39 | .decor_mut() 40 | .set_prefix("\n# The profile that 'dist' will build with\n") 41 | } 42 | dist_profile.or_insert(new_profile); 43 | 44 | Ok(true) 45 | } 46 | -------------------------------------------------------------------------------- /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/axodotdev/cargo-dist/releases/latest). 12 | 13 | The package names all currently use the original name, `cargo-dist`. In the future, some or all of these packages will be renamed to just `dist`. 14 | 15 | ### Installer scripts 16 | 17 | #### macOS and Linux (not NixOS): 18 | 19 | ```sh 20 | curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/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/axodotdev/cargo-dist/releases/latest/download/cargo-dist-installer.ps1 | iex" 27 | ``` 28 | 29 | ### Package managers 30 | 31 | #### Homebrew 32 | 33 | ```sh 34 | brew install axodotdev/tap/cargo-dist 35 | ``` 36 | 37 | #### Pacman (Arch Linux) 38 | 39 | Arch Linux users can install the `cargo-dist` package from the [extra repository](https://archlinux.org/packages/extra/x86_64/cargo-dist/) using [pacman](https://wiki.archlinux.org/title/Pacman): 40 | 41 | ```sh 42 | pacman -S cargo-dist 43 | ``` 44 | 45 | #### Nix (NixOS, macOS) 46 | 47 | Nix users can install `cargo-dist` from the main nixpkgs repository: 48 | 49 | ```sh 50 | nix-env -i cargo-dist 51 | ``` 52 | 53 | ### Other Options 54 | 55 | #### cargo-binstall 56 | 57 | ```sh 58 | cargo binstall cargo-dist 59 | ``` 60 | 61 | ## Build From Source 62 | 63 | For users who need to install dist on platforms that we do not yet provide pre-built binaries for, you will need to build from source. 64 | `dist` is written in [Rust] and uses [cargo] to build. Once you've [installed the Rust toolchain (`rustup`)], run: 65 | 66 | ```sh 67 | cargo install cargo-dist --locked 68 | ``` 69 | 70 | [Rust]: https://rust-lang.org 71 | [cargo]: https://doc.rust-lang.org/cargo/index.html 72 | [installed the Rust toolchain (`rustup`)]: https://rustup.rs/ 73 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cargo-dist/tests/snapshots/lib_manifest.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: cargo-dist/tests/cli-tests.rs 3 | expression: format_outputs(&output) 4 | snapshot_kind: text 5 | --- 6 | stdout: 7 | { 8 | "dist_version": "1.0.0-FAKEVERSION", 9 | "announcement_tag": "CENSORED", 10 | "announcement_tag_is_implicit": false, 11 | "announcement_is_prerelease": "CENSORED" 12 | "announcement_title": "CENSORED" 13 | "announcement_changelog": "CENSORED" 14 | "announcement_github_body": "CENSORED" 15 | "releases": [ 16 | { 17 | "app_name": "dist-schema", 18 | "app_version": "1.0.0-FAKEVERSION", 19 | "env": { 20 | "install_dir_env_var": "DIST_SCHEMA_INSTALL_DIR", 21 | "unmanaged_dir_env_var": "DIST_SCHEMA_UNMANAGED_INSTALL", 22 | "disable_update_env_var": "DIST_SCHEMA_DISABLE_UPDATE", 23 | "no_modify_path_env_var": "DIST_SCHEMA_NO_MODIFY_PATH", 24 | "github_base_url_env_var": "DIST_SCHEMA_INSTALLER_GITHUB_BASE_URL", 25 | "ghe_base_url_env_var": "DIST_SCHEMA_INSTALLER_GHE_BASE_URL" 26 | }, 27 | "display_name": "dist-schema", 28 | "display": true, 29 | "hosting": { 30 | "github": { 31 | "artifact_base_url": "https://github.com", 32 | "artifact_download_path": "/axodotdev/cargo-dist/releases/download/dist-schema-v1.0.0-FAKEVERSION", 33 | "owner": "axodotdev", 34 | "repo": "cargo-dist" 35 | }, 36 | "axodotdev": { 37 | "package": "dist-schema", 38 | "public_id": "fake-id-do-not-upload", 39 | "set_download_url": "https://fake.axo.dev/faker/dist-schema/fake-id-do-not-upload", 40 | "upload_url": null, 41 | "release_url": null, 42 | "announce_url": null 43 | } 44 | } 45 | } 46 | ], 47 | "systems": { 48 | "plan:all:": { 49 | "id": "plan:all:", 50 | "cargo_version_line": "CENSORED" 51 | "build_environment": "indeterminate" 52 | } 53 | }, 54 | "publish_prereleases": false, 55 | "force_latest": false, 56 | "ci": { 57 | "github": { 58 | "artifacts_matrix": {}, 59 | "pr_run_mode": "plan" 60 | } 61 | }, 62 | "linkage": [], 63 | "upload_files": [], 64 | "github_attestations": true 65 | } 66 | 67 | stderr: 68 | INFO: You've enabled Axo Releases, which is currently in Closed Beta. 69 | If you haven't yet signed up, please join our discord 70 | (https://discord.gg/ECnWuUUXQk) or message hello@axo.dev to get started! 71 | -------------------------------------------------------------------------------- /cargo-dist/tests/snapshots/lib_manifest_slash.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: cargo-dist/tests/cli-tests.rs 3 | expression: format_outputs(&output) 4 | snapshot_kind: text 5 | --- 6 | stdout: 7 | { 8 | "dist_version": "1.0.0-FAKEVERSION", 9 | "announcement_tag": "CENSORED", 10 | "announcement_tag_is_implicit": false, 11 | "announcement_is_prerelease": "CENSORED" 12 | "announcement_title": "CENSORED" 13 | "announcement_changelog": "CENSORED" 14 | "announcement_github_body": "CENSORED" 15 | "releases": [ 16 | { 17 | "app_name": "dist-schema", 18 | "app_version": "1.0.0-FAKEVERSION", 19 | "env": { 20 | "install_dir_env_var": "DIST_SCHEMA_INSTALL_DIR", 21 | "unmanaged_dir_env_var": "DIST_SCHEMA_UNMANAGED_INSTALL", 22 | "disable_update_env_var": "DIST_SCHEMA_DISABLE_UPDATE", 23 | "no_modify_path_env_var": "DIST_SCHEMA_NO_MODIFY_PATH", 24 | "github_base_url_env_var": "DIST_SCHEMA_INSTALLER_GITHUB_BASE_URL", 25 | "ghe_base_url_env_var": "DIST_SCHEMA_INSTALLER_GHE_BASE_URL" 26 | }, 27 | "display_name": "dist-schema", 28 | "display": true, 29 | "hosting": { 30 | "github": { 31 | "artifact_base_url": "https://github.com", 32 | "artifact_download_path": "/axodotdev/cargo-dist/releases/download/dist-schema/v1.0.0-FAKEVERSION", 33 | "owner": "axodotdev", 34 | "repo": "cargo-dist" 35 | }, 36 | "axodotdev": { 37 | "package": "dist-schema", 38 | "public_id": "fake-id-do-not-upload", 39 | "set_download_url": "https://fake.axo.dev/faker/dist-schema/fake-id-do-not-upload", 40 | "upload_url": null, 41 | "release_url": null, 42 | "announce_url": null 43 | } 44 | } 45 | } 46 | ], 47 | "systems": { 48 | "plan:all:": { 49 | "id": "plan:all:", 50 | "cargo_version_line": "CENSORED" 51 | "build_environment": "indeterminate" 52 | } 53 | }, 54 | "publish_prereleases": false, 55 | "force_latest": false, 56 | "ci": { 57 | "github": { 58 | "artifacts_matrix": {}, 59 | "pr_run_mode": "plan" 60 | } 61 | }, 62 | "linkage": [], 63 | "upload_files": [], 64 | "github_attestations": true 65 | } 66 | 67 | stderr: 68 | INFO: You've enabled Axo Releases, which is currently in Closed Beta. 69 | If you haven't yet signed up, please join our discord 70 | (https://discord.gg/ECnWuUUXQk) or message hello@axo.dev to get started! 71 | -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-create-react-app/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cargo-dist/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "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 | 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 | -------------------------------------------------------------------------------- /cargo-dist/src/config/version.rs: -------------------------------------------------------------------------------- 1 | //! For determining which configuration version we're using. 2 | 3 | use axoasset::SourceFile; 4 | use camino::Utf8PathBuf; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use crate::DistResult; 8 | 9 | /// Represents all known configuration versions. 10 | #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] 11 | pub enum ConfigVersion { 12 | /// The original legacy configuration formats are all lumped in as V0. 13 | #[serde(rename = "0")] 14 | V0 = 0, 15 | /// The current configuration format. 16 | #[serde(rename = "1")] 17 | #[default] 18 | V1 = 1, 19 | } 20 | 21 | impl std::fmt::Display for ConfigVersion { 22 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 23 | write!(f, "{}", self.clone() as i64) 24 | } 25 | } 26 | 27 | // Extremely minimal struct designed to differentiate between config versions. 28 | // V0 does not have the `config-version` field, so will fail to parse. 29 | // V1+ should have it, so will parse, and contain a `config_version` field. 30 | #[derive(Deserialize)] 31 | #[serde(rename_all = "kebab-case")] 32 | struct FauxDistTable { 33 | #[allow(dead_code)] 34 | config_version: ConfigVersion, 35 | } 36 | 37 | #[derive(Deserialize)] 38 | struct FauxConfig { 39 | #[allow(dead_code)] 40 | dist: FauxDistTable, 41 | } 42 | 43 | /// Return the config version used for the root workspace. 44 | pub fn get_version() -> DistResult { 45 | let workspaces = super::get_project()?; 46 | let root_workspace = workspaces.root_workspace(); 47 | 48 | get_version_for_manifest(root_workspace.manifest_path.to_owned()) 49 | } 50 | 51 | /// Given a path to a dist manifest (e.g. `dist-workspace.toml`), returns 52 | /// the config version being used. 53 | pub fn get_version_for_manifest(dist_manifest_path: Utf8PathBuf) -> DistResult { 54 | if dist_manifest_path.file_name() != Some("dist-workspace.toml") { 55 | // If the manifest is in Cargo.toml or dist.toml, we're 56 | // definitely using a v0 config. 57 | return Ok(ConfigVersion::V0); 58 | } 59 | 60 | let src = SourceFile::load_local(&dist_manifest_path)?; 61 | 62 | let Ok(config) = src.deserialize_toml::() else { 63 | // If we could load it, but can't parse it, it's likely v0. 64 | return Ok(ConfigVersion::V0); 65 | }; 66 | 67 | let version = config.dist.config_version; 68 | 69 | Ok(version) 70 | } 71 | 72 | /// Returns true if the project is using a v1 config _or_ if the `DIST_V1` 73 | /// environment variable is set to any value except `false`. 74 | pub fn want_v1() -> DistResult { 75 | let want_v1 = std::env::var("DIST_V1") 76 | .map(|s| s != "false") 77 | .unwrap_or(false); 78 | 79 | Ok(want_v1 || (get_version()? == ConfigVersion::V1)) 80 | } 81 | -------------------------------------------------------------------------------- /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-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 | /// How to checksum 23 | pub checksum: ChecksumStyle, 24 | } 25 | /// artifact config (raw from file) 26 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 27 | #[serde(rename_all = "kebab-case")] 28 | pub struct ArtifactLayer { 29 | /// archive config 30 | #[serde(skip_serializing_if = "Option::is_none")] 31 | pub archives: Option, 32 | 33 | /// Whether to generate and dist a tarball containing your app's source code 34 | /// 35 | /// (defaults to true) 36 | #[serde(skip_serializing_if = "Option::is_none")] 37 | pub source_tarball: Option, 38 | 39 | /// Any extra artifacts and their buildscripts 40 | #[serde(skip_serializing_if = "Option::is_none")] 41 | pub extra: Option>, 42 | 43 | /// How to checksum 44 | #[serde(skip_serializing_if = "Option::is_none")] 45 | pub checksum: Option, 46 | } 47 | impl AppArtifactConfig { 48 | /// get the defaults for a package 49 | pub fn defaults_for_package(workspaces: &WorkspaceGraph, pkg_idx: PackageIdx) -> Self { 50 | Self { 51 | archives: ArchiveConfig::defaults_for_package(workspaces, pkg_idx), 52 | extra: vec![], 53 | } 54 | } 55 | } 56 | 57 | impl WorkspaceArtifactConfig { 58 | /// get the defaults for a workspace 59 | pub fn defaults_for_workspace(_workspaces: &WorkspaceGraph) -> Self { 60 | Self { 61 | source_tarball: true, 62 | checksum: ChecksumStyle::Sha256, 63 | } 64 | } 65 | } 66 | 67 | impl ApplyLayer for AppArtifactConfig { 68 | type Layer = ArtifactLayer; 69 | fn apply_layer( 70 | &mut self, 71 | Self::Layer { 72 | archives, 73 | extra, 74 | // these are all workspace-only 75 | source_tarball: _, 76 | checksum: _, 77 | }: Self::Layer, 78 | ) { 79 | self.archives.apply_val_layer(archives); 80 | self.extra.apply_val(extra); 81 | } 82 | } 83 | 84 | impl ApplyLayer for WorkspaceArtifactConfig { 85 | type Layer = ArtifactLayer; 86 | fn apply_layer( 87 | &mut self, 88 | Self::Layer { 89 | source_tarball, 90 | checksum, 91 | // these are all app-only 92 | archives: _, 93 | extra: _, 94 | }: Self::Layer, 95 | ) { 96 | self.source_tarball.apply_val(source_tarball); 97 | self.checksum.apply_val(checksum); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /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/axodotdev/cargo-dist" 14 | homepage = "https://opensource.axo.dev/cargo-dist/" 15 | version = "1.0.0-rc.1" 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 | dist-schema = { version = "=1.0.0-rc.1", path = "cargo-dist-schema" } 21 | axoproject = { version = "=1.0.0-rc.1", 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.28", features = ["derive"] } 33 | console = { version = "0.15.10" } 34 | clap-cargo = { version = "0.14.0" } 35 | comfy-table = "7.1.4" 36 | miette = { version = "7.5.0" } 37 | thiserror = "2.0.11" 38 | tracing = { version = "0.1.41", features = ["log"] } 39 | serde = { version = "1.0.217", features = ["derive"] } 40 | serde_json = { version = "1.0.138" } 41 | cargo_metadata = "0.18.1" 42 | camino = { version = "1.1.9", features = ["serde1"] } 43 | semver = "1.0.25" 44 | newline-converter = "0.3.0" 45 | dialoguer = "0.11.0" 46 | sha2 = "0.10.6" 47 | minijinja = { version = "2.6.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.7.0" 55 | tokio = { version = "1.43.0", features = ["full"] } 56 | temp-dir = "0.1.14" 57 | sha3 = "0.10.8" 58 | blake2 = "0.10.6" 59 | insta = { version = "1.42.1", 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.12" 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 = "0.12.16" 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/axodotdev/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 | -------------------------------------------------------------------------------- /.github/workflows/web.yml: -------------------------------------------------------------------------------- 1 | # Workflow to build your docs with oranda (and mdbook) 2 | # and deploy them to Github Pages 3 | name: Web 4 | 5 | # We're going to push to the gh-pages branch, so we need that permission 6 | permissions: 7 | contents: write 8 | 9 | # What situations do we want to build docs in? 10 | # All of these work independently and can be removed / commented out 11 | # if you don't want oranda/mdbook running in that situation 12 | on: 13 | # Check that a PR didn't break docs! 14 | # 15 | # Note that the "Deploy to Github Pages" step won't run in this mode, 16 | # so this won't have any side-effects. But it will tell you if a PR 17 | # completely broke oranda/mdbook. Sadly we don't provide previews (yet)! 18 | pull_request: 19 | 20 | # Whenever something gets pushed to main, update the docs! 21 | # This is great for getting docs changes live without cutting a full release. 22 | # 23 | # Note that if you're using dist, this will "race" the Release workflow 24 | # that actually builds the Github Release that oranda tries to read (and 25 | # this will almost certainly complete first). As a result you will publish 26 | # docs for the latest commit but the oranda landing page won't know about 27 | # the latest release. The workflow_run trigger below will properly wait for 28 | # dist, and so this half-published state will only last for ~10 minutes. 29 | # 30 | # If you only want docs to update with releases, disable this one. 31 | push: 32 | branches: 33 | - main 34 | 35 | # Whenever a workflow called "Release" completes, update the docs! 36 | # 37 | # If you're using dist, this is recommended, as it will ensure that 38 | # oranda always sees the latest release right when it's available. Note 39 | # however that Github's UI is wonky when you use workflow_run, and won't 40 | # show this workflow as part of any commit. You have to go to the "actions" 41 | # tab for your repo to see this one running (the gh-pages deploy will also 42 | # only show up there). 43 | workflow_run: 44 | workflows: ["Release"] 45 | types: 46 | - completed 47 | 48 | # Alright, let's do it! 49 | jobs: 50 | web: 51 | name: Build and deploy site and docs 52 | runs-on: ubuntu-latest 53 | steps: 54 | # Setup 55 | - uses: actions/checkout@v3 56 | with: 57 | fetch-depth: 0 58 | - uses: dtolnay/rust-toolchain@stable 59 | - uses: swatinem/rust-cache@v2 60 | 61 | # Install mdbook plugins 62 | - name: Install mdbook plugins 63 | run: | 64 | cargo install --locked mdbook-toc@0.11.2 65 | cargo install --locked mdbook-linkcheck@0.7.7 66 | 67 | # Install and run oranda (and mdbook) 68 | # This will write all output to ./public/ (including copying mdbook's output to there) 69 | - name: Install and run oranda 70 | run: | 71 | curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/oranda/releases/latest/download/oranda-installer.sh | sh 72 | cd cargo-dist 73 | oranda build 74 | 75 | # Deploy to our gh-pages branch (making it if it doesn't exist) 76 | # the "public" dir that oranda made above will become the root dir 77 | # of this branch. 78 | # 79 | # Note that once the gh-pages branch exists, you must 80 | # go into repo's settings > pages and set "deploy from branch: gh-pages" 81 | # the other defaults work fine. 82 | - name: Deploy to Github Pages 83 | uses: JamesIves/github-pages-deploy-action@v4.4.1 84 | # ONLY if we're on main (so no PRs or feature branches allowed!) 85 | if: ${{ github.ref == 'refs/heads/main' }} 86 | with: 87 | branch: gh-pages 88 | # Gotta tell the action where to find oranda's output 89 | folder: cargo-dist/public 90 | token: ${{ secrets.GITHUB_TOKEN }} 91 | single-commit: true 92 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | } 19 | 20 | /// archive config (raw from config file) 21 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 22 | #[serde(rename_all = "kebab-case")] 23 | pub struct ArchiveLayer { 24 | /// Include the following static files in bundles like archives. 25 | /// 26 | /// Paths are relative to the Cargo.toml this is defined in. 27 | /// 28 | /// Files like `README*`, `(UN)LICENSE*`, `RELEASES*`, and `CHANGELOG*` are already 29 | /// automatically detected and included (use [`DistMetadata::auto_includes`][] to prevent this). 30 | #[serde(skip_serializing_if = "Option::is_none")] 31 | pub include: Option>, 32 | 33 | /// Whether to auto-include files like `README*`, `(UN)LICENSE*`, `RELEASES*`, and `CHANGELOG*` 34 | /// 35 | /// Defaults to true. 36 | #[serde(skip_serializing_if = "Option::is_none")] 37 | pub auto_includes: Option, 38 | 39 | /// The archive format to use for windows builds (defaults .zip) 40 | #[serde(skip_serializing_if = "Option::is_none")] 41 | pub windows_archive: Option, 42 | 43 | /// The archive format to use for non-windows builds (defaults .tar.xz) 44 | #[serde(skip_serializing_if = "Option::is_none")] 45 | pub unix_archive: Option, 46 | 47 | /// Whether to include built libraries in the release archive 48 | #[serde(skip_serializing_if = "Option::is_none")] 49 | #[serde(default, with = "opt_string_or_vec")] 50 | pub package_libraries: Option>, 51 | } 52 | 53 | impl ArchiveConfig { 54 | /// Get defaults for the given package 55 | pub fn defaults_for_package(_workspaces: &WorkspaceGraph, _pkg_idx: PackageIdx) -> Self { 56 | Self { 57 | include: vec![], 58 | auto_includes: true, 59 | windows_archive: ZipStyle::Zip, 60 | unix_archive: ZipStyle::Tar(CompressionImpl::Xzip), 61 | package_libraries: vec![], 62 | } 63 | } 64 | } 65 | 66 | impl ApplyLayer for ArchiveConfig { 67 | type Layer = ArchiveLayer; 68 | fn apply_layer( 69 | &mut self, 70 | Self::Layer { 71 | include, 72 | auto_includes, 73 | windows_archive, 74 | unix_archive, 75 | package_libraries, 76 | }: Self::Layer, 77 | ) { 78 | self.include.apply_val(include); 79 | self.auto_includes.apply_val(auto_includes); 80 | self.windows_archive.apply_val(windows_archive); 81 | self.unix_archive.apply_val(unix_archive); 82 | self.package_libraries.apply_val(package_libraries); 83 | } 84 | } 85 | impl ApplyLayer for ArchiveLayer { 86 | type Layer = ArchiveLayer; 87 | fn apply_layer( 88 | &mut self, 89 | Self::Layer { 90 | include, 91 | auto_includes, 92 | windows_archive, 93 | unix_archive, 94 | package_libraries, 95 | }: Self::Layer, 96 | ) { 97 | self.include.apply_opt(include); 98 | self.auto_includes.apply_opt(auto_includes); 99 | self.windows_archive.apply_opt(windows_archive); 100 | self.unix_archive.apply_opt(unix_archive); 101 | self.package_libraries.apply_opt(package_libraries); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /cargo-dist/src/config/v1/loader.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | config::{self, v1::DistConfig, v1::TomlLayer}, 3 | errors::{DistError, DistResult}, 4 | }; 5 | use axoasset::SourceFile; 6 | use camino::Utf8Path; 7 | 8 | /// Load the dist(-workspace).toml for the root workspace. 9 | pub fn load_root() -> DistResult { 10 | let workspaces = config::get_project()?; 11 | let root_workspace = workspaces.root_workspace(); 12 | 13 | let Some(path) = root_workspace.dist_manifest_path.as_deref() else { 14 | return Err(DistError::NoConfigFile {}); 15 | }; 16 | 17 | load(path) 18 | } 19 | 20 | /// Loads a dist(-workspace).toml from disk. 21 | pub fn load(dist_manifest_path: &Utf8Path) -> DistResult { 22 | let src = SourceFile::load_local(dist_manifest_path)?; 23 | parse(src) 24 | } 25 | 26 | /// Load a dist(-workspace).toml from disk and return its `[dist]` table. 27 | pub fn load_dist(dist_manifest_path: &Utf8Path) -> DistResult { 28 | Ok(load(dist_manifest_path)?.dist.unwrap_or_default()) 29 | } 30 | 31 | /// Given a SourceFile of a dist(-workspace).toml, deserializes it. 32 | pub fn parse(src: SourceFile) -> DistResult { 33 | // parse() can probably be consolidated into load() eventually. 34 | Ok(src.deserialize_toml()?) 35 | } 36 | 37 | /// Given a SourceFile of a dist(-workspace).toml, deserialize its `[dist]` table. 38 | pub fn parse_dist(src: SourceFile) -> DistResult { 39 | Ok(parse(src)?.dist.unwrap_or_default()) 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use super::*; 45 | use axoasset::SourceFile; 46 | 47 | #[test] 48 | fn parse_v1_succeeds() { 49 | let file = SourceFile::new("fake-v1-dist-workspace.toml", r##" 50 | [workspace] 51 | members = ["cargo:*"] 52 | 53 | [package] 54 | name = "whatever" 55 | version = "1.0.0" 56 | 57 | # Config for 'dist' 58 | [dist] 59 | dist-version = "1.0.0" 60 | 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"] 61 | 62 | [dist.build] 63 | min-glibc-version."*" = "2.18" 64 | 65 | [dist.ci] 66 | github = true 67 | pr-run-mode = "plan" 68 | #publish-jobs = ["homebrew", "./publish-crates"] 69 | 70 | [dist.hosts] 71 | github = true 72 | 73 | [dist.installers] 74 | install-path = "CARGO_HOME" 75 | shell = true 76 | updater = false 77 | 78 | [dist.installers.homebrew] 79 | tap = "axodotdev/homebrew-tap" 80 | "##.to_string()); 81 | 82 | assert!(parse(file).is_ok()); 83 | } 84 | 85 | #[test] 86 | fn parse_v0_fails() { 87 | let file = SourceFile::new("fake-v0-dist-workspace.toml", r##" 88 | [workspace] 89 | members = ["cargo:*"] 90 | 91 | [package] 92 | name = "whatever" 93 | version = "1.0.0" 94 | 95 | # Config for 'dist' 96 | [dist] 97 | # The preferred dist version to use in CI (Cargo.toml SemVer syntax) 98 | cargo-dist-version = "0.13.1-prerelease.1" 99 | # CI backends to support 100 | ci = "github" 101 | # The installers to generate for each app 102 | installers = ["shell", "powershell", "homebrew"] 103 | # A GitHub repo to push Homebrew formulas to 104 | tap = "axodotdev/homebrew-tap" 105 | # Publish jobs to run in CI 106 | publish-jobs = ["homebrew", "./publish-crates"] 107 | # Target platforms to build apps for (Rust target-triple syntax) 108 | 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"] 109 | # Which actions to run on pull requests 110 | pr-run-mode = "plan" 111 | # Where to host releases 112 | hosting = ["github"] 113 | # Whether to install an updater program 114 | install-updater = false 115 | # Path that installers should place binaries in 116 | install-path = "CARGO_HOME" 117 | # The minimum glibc version supported by the package (overrides auto-detection) 118 | min-glibc-version."*" = "2.18" 119 | "##.to_string()); 120 | 121 | assert!(parse(file).is_err()); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /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]). 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 | * the value is written back with a `REG_EXPAND_SZ` data type, which enables PATH expansion 43 | * if we edited the registry, we prompt the user to restart their shell 44 | * we additionally broadcast `WM_SETTINGCHANGE` to running programs to ensure that environment updates are propagated. This ensures you only need to restart your shell, rather than your entire machine, for the "Path" update to be recognized. 45 | 46 | 47 | 48 | 49 | [issue-irm-iex]: https://github.com/axodotdev/oranda/issues/393 50 | [issue-musl]: https://github.com/axodotdev/cargo-dist/issues/75 51 | [issue-unpack-all]: https://github.com/axodotdev/cargo-dist/issues/307 52 | 53 | [config-install-path]: ../reference/config.md#install-path 54 | 55 | [archive]: ../artifacts/archives.md 56 | [artifact-url]: ../reference/artifact-url.md 57 | 58 | [cargo home]: https://doc.rust-lang.org/cargo/guide/cargo-home.html 59 | -------------------------------------------------------------------------------- /cargo-dist/src/config/v1/publishers/mod.rs: -------------------------------------------------------------------------------- 1 | //! publisher config 2 | 3 | pub mod homebrew; 4 | pub mod npm; 5 | 6 | use super::*; 7 | 8 | use homebrew::*; 9 | use npm::*; 10 | 11 | /// the final publisher config 12 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 13 | pub struct PublisherConfig { 14 | /// homebrew publisher 15 | pub homebrew: Option, 16 | /// npm publisher 17 | pub npm: Option, 18 | } 19 | 20 | /// the publisher config 21 | /// 22 | /// but with inheritance not yet folded in 23 | #[derive(Debug, Clone)] 24 | pub struct PublisherConfigInheritable { 25 | /// common fields that each publisher inherits 26 | pub common: CommonPublisherConfig, 27 | /// homebrew publisher 28 | pub homebrew: Option, 29 | /// npm publisher 30 | pub npm: Option, 31 | } 32 | 33 | /// "raw" publisher config from presum 34 | #[derive(Debug, Clone, Serialize, Deserialize)] 35 | pub struct PublisherLayer { 36 | /// common fields that each publisher inherits 37 | #[serde(flatten)] 38 | pub common: CommonPublisherLayer, 39 | /// homebrew publisher 40 | pub homebrew: Option>, 41 | /// npm publisher 42 | pub npm: Option>, 43 | } 44 | impl PublisherConfigInheritable { 45 | /// get the defaults for a given package 46 | pub fn defaults_for_package(workspaces: &WorkspaceGraph, pkg_idx: PackageIdx) -> Self { 47 | Self { 48 | common: CommonPublisherConfig::defaults_for_package(workspaces, pkg_idx), 49 | homebrew: None, 50 | npm: None, 51 | } 52 | } 53 | /// fold the inherited fields in to get the final publisher config 54 | pub fn apply_inheritance_for_package( 55 | self, 56 | workspaces: &WorkspaceGraph, 57 | pkg_idx: PackageIdx, 58 | ) -> PublisherConfig { 59 | let Self { 60 | common, 61 | homebrew, 62 | npm, 63 | } = self; 64 | let homebrew = homebrew.map(|homebrew| { 65 | let mut default = 66 | HomebrewPublisherConfig::defaults_for_package(workspaces, pkg_idx, &common); 67 | default.apply_layer(homebrew); 68 | default 69 | }); 70 | let npm = npm.map(|npm| { 71 | let mut default = 72 | NpmPublisherConfig::defaults_for_package(workspaces, pkg_idx, &common); 73 | default.apply_layer(npm); 74 | default 75 | }); 76 | PublisherConfig { homebrew, npm } 77 | } 78 | } 79 | impl ApplyLayer for PublisherConfigInheritable { 80 | type Layer = PublisherLayer; 81 | fn apply_layer( 82 | &mut self, 83 | Self::Layer { 84 | common, 85 | homebrew, 86 | npm, 87 | }: Self::Layer, 88 | ) { 89 | self.common.apply_layer(common); 90 | self.homebrew.apply_bool_layer(homebrew); 91 | self.npm.apply_bool_layer(npm); 92 | } 93 | } 94 | 95 | /// fields that each publisher inherits (raw) 96 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 97 | pub struct CommonPublisherLayer { 98 | /// Whether to publish prereleases (defaults to false) 99 | #[serde(skip_serializing_if = "Option::is_none")] 100 | pub prereleases: Option, 101 | } 102 | /// fields that each publisher inherits (final) 103 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 104 | pub struct CommonPublisherConfig { 105 | /// Whether to publish prereleases (defaults to false) 106 | pub prereleases: bool, 107 | } 108 | impl CommonPublisherConfig { 109 | /// get the defaults for a given package 110 | pub fn defaults_for_package(_workspaces: &WorkspaceGraph, _pkg_idx: PackageIdx) -> Self { 111 | Self { prereleases: false } 112 | } 113 | } 114 | impl ApplyLayer for CommonPublisherConfig { 115 | type Layer = CommonPublisherLayer; 116 | fn apply_layer(&mut self, Self::Layer { prereleases }: Self::Layer) { 117 | self.prereleases.apply_val(prereleases); 118 | } 119 | } 120 | impl ApplyLayer for CommonPublisherLayer { 121 | type Layer = CommonPublisherLayer; 122 | fn apply_layer(&mut self, Self::Layer { prereleases }: Self::Layer) { 123 | self.prereleases.apply_opt(prereleases); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 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-20.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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------