1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
//! A library for downloading and installing pre-built binaries from GitHub.
//!
//! UBI stands for "Universal Binary Installer". It downloads and installs pre-built binaries from
//! GitHub releases. It is designed to be used in shell scripts and other automation.
//!
//! This project also ships a CLI tool named `ubi`. See [the project's GitHub
//! repo](https://github.com/houseabsolute/ubi) for more details on installing and using this tool.
//!
//! The main entry point for programmatic use is the [`UbiBuilder`] struct. Here is an example of its
//! usage:
//!
//! ```ignore
//! use ubi::UbiBuilder;
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let ubi = UbiBuilder::new()
//! .project("houseabsolute/precious")
//! .install_dir("/usr/local/bin")
//! .build()?;
//!
//! ubi.install_binary().await?;
//!
//! Ok(())
//! }
//! ```
//!
//! ## Installed Executable Naming
//!
//! If the release is in the form of a tarball or zip file, `ubi` will look in that archive file for
//! a file that matches the value given for the `exe` field, if any. Otherwise it looks for a file
//! with the same name as the project. In either case, the file will be installed with the name it
//! has in the archive file.
//!
//! If the release is in the form of a bare executable or a compressed executable, then the
//! installed executable will use the name of the project instead. For files with a `.exe`, `.jar`,
//! `.phar`, `.py`, `.pyz` `.sh`, or `.AppImage`, the installed executable will be
//! `$project_name.$extension`.
//!
//! This is a bit inconsistent, but it's how `ubi` has behaved since it was created, and I find this
//! to be the sanest behavior. Some projects, for example `rust-analyzer`, provide releases as
//! executables with names like `rust-analyzer-x86_64-apple-darwin` and
//! `rust-analyzer-x86_64-unknown-linux-musl`, so installing these as `rust-analyzer` seems like
//! better behavior.
//!
//! ## How `ubi` Finds the Right Release Artifact
//!
//! <div class="warning">Note that the exact set of steps that are followed to find a release
//! artifacts is not considered part of the API, and may change in any future release.</div>
//!
//! If you work on a project and you'd like to make sure that `ubi` can install it, please see [my
//! blog post, Naming Your Binary Executable
//! Releases](https://blog.urth.org/2023/04/16/naming-your-binary-executable-releases/) for more
//! details.
//!
//! When you call [`Ubi::install_binary`], it looks at the release assets (downloadable files) for a
//! project and tries to find the "right" asset for the platform it's running on. The matching logic
//! currently works like this:
//!
//! First it filters out assets with extensions it doesn't recognize. Right now this is anything that
//! doesn't match one of the following:
//!
//! - `.7z`
//! - `.AppImage` (Linux only)
//! - `.bat` (Windows only)
//! - `.bz`
//! - `.bz2`
//! - `.exe` (Windows only)
//! - `.gz`
//! - `.jar`
//! - `.phar`
//! - `.py`
//! - `.pyz`
//! - `.sh`
//! - `.tar`
//! - `.tar.bz`
//! - `.tar.bz2`
//! - `.tar.gz`
//! - `.tar.xz`
//! - `.tbz`
//! - `.tgz`
//! - `.txz`
//! - `.xz`
//! - `.zip`
//! - No extension
//!
//! It tries to be careful about what constitutes an extension. It's common for release filenames to
//! include a dot (`.`) in the filename before something that's _not_ intended as an extension, for
//! example `some-tool.linux.amd64` or `some-tools-linux-x86-64-1.3.5.tar.gz`.
//!
//! If, after filtering for extensions, there's only one asset, it will try to install this one, on
//! the assumption that this project releases assets which are not platform-specific (like a shell
//! script) _or_ that this project only releases for one platform and you're running `ubi` on that
//! platform.
//!
//! If there are multiple matching assets, it will first filter them based on your platform. It does
//! this in several stages:
//!
//! - First it filters based on your OS, which is something like Linux, macOS, Windows, FreeBSD,
//! etc. It looks at the asset filenames to see which ones match your OS, using a (hopefully
//! complete) regex.
//! - Next it filters based on your CPU architecture, which is something like x86-64, ARM64, `PowerPC`,
//! etc. Again, this is done with a regex.
//! - If you are running on a Linux system using musl as its libc, it will also filter out anything
//! _not_ compiled against musl. This filter looks to see if the file name contains an indication
//! of which libc it was compiled against. Typically, this is something like "-gnu" or "-musl". If
//! it does contain this indicator, names that are _not_ musl are filtered out. However, if there
//! is no libc indicator, the asset will still be included. You can use the
//! [`UbiBuilder::is_musl`] method to explicitly say that the platform is using musl. If this
//! isn't set, then it will try to detect if you are using musl by looking at the output of `ldd
//! /bin/ls`. However, if there is no libc indicator, the asset will still be included, but musl
//! assets will be preferred over assets with no indication of which libc they use.
//!
//! At this point, any remaining assets should work on your platform, so if there's more than one
//! match, it attempts to pick the best one.
//!
//! - If it finds both 64-bit and 32-bit assets and you are on a 64-bit platform, it filters out the
//! 32-bit assets.
//! - If you've provided a string to [`UbiBuilder::matching`], this is used as a filter at this
//! point.
//! - If your platform is macOS on ARM64 and there are assets for both x86-64 and ARM64, it filters
//! out the non-ARM64 assets.
//!
//! Finally, if there are still multiple assets left, it sorts them by file name and picks the first
//! one. The sorting is done to make sure it always picks the same one every time it's run .
//!
//! ## How `ubi` Finds the Right Executable in an Archive File
//!
//! If the selected release artifact is an archive file (a tarball or zip file), then `ubi` will
//! look inside the archive to find the right executable.
//!
//! It first tries to find a file matching the exact name of the project (plus an extension on
//! Windows). So for example, if you're installing
//! [`houseabsolute/precious`](https://github.com/houseabsolute/precious), it will look in the
//! archive for a file named `precious` on Unix-like systems and `precious.bat` or `precious.exe` on
//! Windows. Note that if it finds an exact match, it does not check the file's mode.
//!
//! If it can't find an exact match it will look for a file that _starts with_ the project
//! name. This is mostly to account for projects that include things like platforms or release names
//! in their executables. Using
//! [`houseabsolute/precious`](https://github.com/houseabsolute/precious) as an example again, it
//! will match a file named `precious-linux-amd64` or `precious-v1.2.3`. In this case, it will
//! _rename_ the extracted file to `precious`. On Unix-like systems, these partial matches will only
//! be considered if the file's mode includes an executable bit. On Windows, it looks for a partial
//! match that is a `.bat` or `.exe` file, and the extracted file will be renamed to `precious.bat`
//! or `precious.exe`.
//!
//! ## Features
//!
//! This crate offers several features to control the TLS dependency used by `reqwest`:
//!
pub use crate::;
// The version of the `ubi` crate.
pub const VERSION: &str = env!;
use ;
/// This function initializes logging for the application. It's public for the sake of the `ubi`
/// binary, but it lives in the library crate so that test code can also enable logging.
///
/// # Errors
///
/// This can return a `log::SetLoggerError` error.