| 1 | --- |
| 2 | title: Cargo |
| 3 | description: Publish and add a workspace's Rust crates with Cargo, from a sparse registry of its own on g1t.sh, from your machine and from workflows. |
| 4 | --- |
| 5 | |
| 6 | Every workspace has a Cargo registry of its own: a sparse index, with the |
| 7 | web API that `cargo publish`, `cargo yank` and `cargo search` use. It |
| 8 | works with Cargo 1.74 or later, private crates included. |
| 9 | |
| 10 | ```text |
| 11 | sparse+https://g1t.sh/-/cargo/<workspace>/index/ |
| 12 | ``` |
| 13 | |
| 14 | Crates from crates.io still come from crates.io; only the crates you name |
| 15 | with the workspace's registry come from g1t. |
| 16 | |
| 17 | ## Set up `.cargo/config.toml` |
| 18 | |
| 19 | Name the registry in the project's `.cargo/config.toml`, or in |
| 20 | `~/.cargo/config.toml` for every project. The name you give it is the one |
| 21 | you pass to `--registry`; these pages use the workspace's slug: |
| 22 | |
| 23 | ```toml |
| 24 | [registries.acme] |
| 25 | index = "sparse+https://g1t.sh/-/cargo/acme/index/" |
| 26 | credential-provider = "cargo:token" |
| 27 | ``` |
| 28 | |
| 29 | Cargo sends a token to a registry that asks for one only through a |
| 30 | credential provider named for it. `cargo:token` keeps the token in |
| 31 | `~/.cargo/credentials.toml`, which stays out of the project. To keep it in |
| 32 | your system's keychain instead, name `cargo:wincred` (Windows), |
| 33 | `cargo:macos-keychain` (macOS) or `cargo:libsecret` (Linux). |
| 34 | |
| 35 | Then give Cargo an [access token](https://g1t.sh/settings/tokens): |
| 36 | |
| 37 | ```sh |
| 38 | cargo login --registry acme |
| 39 | ``` |
| 40 | |
| 41 | Cargo asks for the token and hands it to the provider. A token with full |
| 42 | access works; one with scopes needs `packages:read` to add private crates |
| 43 | and `packages:write` to publish and yank them. |
| 44 | |
| 45 | The token can also come from the environment instead of `cargo login`, |
| 46 | as `CARGO_REGISTRIES_ACME_TOKEN` for a registry named `acme` (the name in |
| 47 | capitals, with `-` written `_`). The `cargo:token` provider reads it from |
| 48 | there, which is how [workflows](#in-workflows) give it. |
| 49 | |
| 50 | ## Publish |
| 51 | |
| 52 | Say in `Cargo.toml` where the crate is published and which repository it |
| 53 | comes from: |
| 54 | |
| 55 | ```toml |
| 56 | [package] |
| 57 | name = "http-client" |
| 58 | version = "0.3.1" |
| 59 | edition = "2021" |
| 60 | description = "Our HTTP client" |
| 61 | license = "MIT" |
| 62 | readme = "README.md" |
| 63 | repository = "https://g1t.sh/acme/http-client" |
| 64 | publish = ["acme"] |
| 65 | ``` |
| 66 | |
| 67 | ```sh |
| 68 | cargo publish --registry acme |
| 69 | ``` |
| 70 | |
| 71 | The first publish makes the crate. When its `repository` is a g1t.sh |
| 72 | repository of the same workspace, or a repository is named like the crate |
| 73 | (a crate `http_client` is also matched to a repository `http-client`), it |
| 74 | is linked to that repository and has its visibility and roles: publishing needs Write on it. |
| 75 | Otherwise it is the workspace's, private, and needs the workspace's Write |
| 76 | base permission. See |
| 77 | [who can see and publish a package](/guides/packages/#who-can-see-and-publish-a-package). |
| 78 | |
| 79 | `publish = ["acme"]` keeps the crate from being published to crates.io by |
| 80 | mistake, and lets `cargo publish` leave out `--registry` when it is the |
| 81 | only registry named. |
| 82 | |
| 83 | A crate may depend on crates.io crates and on other crates of the |
| 84 | workspace's registry. The crate's page on g1t.sh shows the README and |
| 85 | description of its highest stable version. |
| 86 | |
| 87 | ## Add a crate |
| 88 | |
| 89 | ```sh |
| 90 | cargo add http-client --registry acme |
| 91 | ``` |
| 92 | |
| 93 | That writes the dependency with its registry into `Cargo.toml`: |
| 94 | |
| 95 | ```toml |
| 96 | [dependencies] |
| 97 | http-client = { version = "0.3.1", registry = "acme" } |
| 98 | ``` |
| 99 | |
| 100 | `Cargo.lock` records each crate's registry and the SHA-256 of its |
| 101 | `.crate` file, which g1t computes when the version is published. |
| 102 | |
| 103 | `cargo search --registry acme http` lists the workspace's crates you can |
| 104 | see whose names match. |
| 105 | |
| 106 | ## Names and versions |
| 107 | |
| 108 | A crate's name follows the crates.io rules: ASCII letters, digits, `-` and |
| 109 | `_`, starting with a letter, at most 64 characters. In a workspace, names |
| 110 | that differ only in case or in `-` against `_` are one name: once |
| 111 | `http-client` is published, `HTTP_Client` is refused. |
| 112 | |
| 113 | A version is published once. Publishing a version that is already there is |
| 114 | refused, as is one that differs from it only in build metadata (`1.0.0+a` |
| 115 | and `1.0.0+b`), so bump `version` first. |
| 116 | |
| 117 | ## Yank |
| 118 | |
| 119 | ```sh |
| 120 | cargo yank --registry acme http-client@0.3.1 |
| 121 | cargo yank --registry acme http-client@0.3.1 --undo |
| 122 | ``` |
| 123 | |
| 124 | A yanked version stays in the registry: projects whose `Cargo.lock` |
| 125 | names it still build, but Cargo no longer picks it for new lockfiles or |
| 126 | `cargo update`. Yanking needs what publishing does. The crate's page marks |
| 127 | yanked versions, and someone with Admin on the linked repository (an |
| 128 | owner, for the workspace's own crates) can delete a version there for |
| 129 | good. |
| 130 | |
| 131 | Crate owners are not kept: who may publish a crate is decided by its |
| 132 | repository's roles, or the workspace's, so `cargo owner` answers with an |
| 133 | error that says so. |
| 134 | |
| 135 | ## Private and public crates |
| 136 | |
| 137 | A crate linked to a repository has the repository's visibility; one of |
| 138 | the workspace's own is private until an owner makes it public on its page. |
| 139 | |
| 140 | | The workspace's crates | Without a token | With a token | |
| 141 | | --- | --- | --- | |
| 142 | | All public | Cargo reads the index and downloads them. | The same; the token is sent to publish and yank. | |
| 143 | | Some private | The registry answers `401`: Cargo needs a token for every crate in it, public ones too. | Cargo sends the token with every request, and sees the crates the token's owner may see. | |
| 144 | |
| 145 | Cargo first asks for the registry's `config.json` without a token. When |
| 146 | the registry answers `401`, Cargo asks again with its token, and the |
| 147 | answer says `"auth-required": true`, so Cargo sends the token from then on. |
| 148 | A private crate you cannot see looks exactly like one that does not exist. |
| 149 | |
| 150 | ## In workflows |
| 151 | |
| 152 | A workflow's `G1T_TOKEN`, [the job's own token](/guides/actions/#the-jobs-token), can install the workspace's crates, |
| 153 | and publish them with `packages: write` in its [`permissions:`](/guides/actions/#the-jobs-token). A workflow runs no `cargo login`: |
| 154 | give Cargo the registry, its credential provider and the token in the |
| 155 | environment, each named for the registry: |
| 156 | |
| 157 | ```yaml |
| 158 | jobs: |
| 159 | publish: |
| 160 | runs-on: ubuntu-latest |
| 161 | permissions: |
| 162 | contents: read |
| 163 | packages: write |
| 164 | env: |
| 165 | CARGO_REGISTRIES_ACME_INDEX: sparse+https://g1t.sh/-/cargo/acme/index/ |
| 166 | CARGO_REGISTRIES_ACME_CREDENTIAL_PROVIDER: cargo:token |
| 167 | CARGO_REGISTRIES_ACME_TOKEN: ${{ secrets.G1T_TOKEN }} |
| 168 | steps: |
| 169 | - uses: actions/checkout@v4 |
| 170 | - run: cargo test |
| 171 | - run: cargo publish --registry acme |
| 172 | ``` |
| 173 | |
| 174 | | Variable | Is | Needed | |
| 175 | | --- | --- | --- | |
| 176 | | `CARGO_REGISTRIES_ACME_INDEX` | The registry's index, as `index` in `.cargo/config.toml`. | When no `.cargo/config.toml` names the registry. | |
| 177 | | `CARGO_REGISTRIES_ACME_CREDENTIAL_PROVIDER` | `cargo:token`, as `credential-provider` in `.cargo/config.toml`. | When no `.cargo/config.toml` names the provider. Without one, Cargo refuses a registry with private crates, even with the token in the environment. | |
| 178 | | `CARGO_REGISTRIES_ACME_TOKEN` | The token, for `cargo:token` to hand to the registry. | Always: `cargo publish` sends it, and Cargo sends it to read a registry with private crates. | |
| 179 | |
| 180 | With the project's `.cargo/config.toml` from [above](#set-up-cargoconfigtoml) |
| 181 | checked in, `CARGO_REGISTRIES_ACME_TOKEN` is all a workflow sets. The same |
| 182 | variables let `cargo build` and `cargo test` download the workspace's |
| 183 | private crates. |
| 184 | |
| 185 | ## Size |
| 186 | |
| 187 | A publish is one request with the `.crate` file inside it, and may hold at |
| 188 | most 100 MB. Without the [g1t plan](/guides/usage-and-billing/#the-g1t-plan), |
| 189 | a workspace's private packages may hold 500 MB and its public ones 10 GB, |
| 190 | as for [container images](/guides/containers/#storage-and-pull-limits). |
| 191 | A `.crate` file is stored once, by its content. |
| 192 | |
| 193 | ## Errors |
| 194 | |
| 195 | | Error | Means | |
| 196 | | --- | --- | |
| 197 | | `401` | No token, or a wrong or expired one. Run `cargo login --registry acme` with a g1t access token, or set `CARGO_REGISTRIES_ACME_TOKEN`. | |
| 198 | | `authenticated registries require a credential-provider to be configured` | The workspace has private crates, and Cargo has no provider for its token. Add `credential-provider = "cargo:token"` to the registry in `.cargo/config.toml`, or set `CARGO_REGISTRIES_ACME_CREDENTIAL_PROVIDER=cargo:token`. | |
| 199 | | `403` | Signed in, but your role or your token's scopes do not allow it, or the workspace is out of free package storage. The message says which. | |
| 200 | | `404` | No such crate or version, or a private one you cannot see. | |
| 201 | | `400` | The publish was refused: a name that is not valid or is taken, a version already published, or metadata Cargo did not send in full. The message says which. | |
| 202 | | `413` | The publish is over 100 MB. Leave large files out with `exclude` or `include` in `Cargo.toml`, and check with `cargo package --list`. | |