Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 26 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,24 @@ You can install the latest version with WinGet:
winget install Microsoft.Edit
```

### Linux (build from source)

If your distribution does not provide binaries, or if you'd like to build your own, you can use our install script, provided you have installed:
* Rust (via `rustup` or similar)
* A C compiler (e.g. `gcc`)
* ICU (e.g. libicu78, libicu, icu)
* curl/wget and tar

The following command will then install `msedit` into `~/.local/bin`:
```sh
curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/microsoft/edit/main/assets/install.sh | sh
```

Additional flags are `--dev`, to build directly from the main branch, and `--system` to install into `/usr/local/bin`. For instance:
```sh
curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/microsoft/edit/main/assets/install.sh | sh -s -- --dev --system
```

## Build Instructions

* [Install Rust](https://www.rust-lang.org/tools/install)
Expand All @@ -37,11 +55,11 @@ winget install Microsoft.Edit

### Build Configuration

Uou can set the following environment variables at build-time to configure the build:
You can set the following environment variables at build-time to configure the build:

Environment variable | Description
--- | ---
`EDIT_CFG_ICU*` | See [ICU library name (SONAME)](#icu-library-name-soname) below for details. This option is particularly important on Linux.
`EDIT_CFG_ICU*` | See [ICU library name (SONAME)](#icu-library-name-soname) below for details. Linux package maintainers are advised to review and configure these options.
`EDIT_CFG_LANGUAGES` | A comma-separated list of languages to include in the build. See [i18n/edit.toml](i18n/edit.toml) for available languages.

## Notes to Package Maintainers
Expand All @@ -57,10 +75,12 @@ Assigning an "edit" alias is recommended, if possible.
This project optionally depends on the ICU library for its Search and Replace functionality.
By default, the project will look for a SONAME without version suffix:
* Windows: `icuuc.dll`
* macOS: `libicuuc.dylib`
* UNIX, and other OS: `libicuuc.so`
By default, the project will look for the following library names:
Variable | Windows | macOS | Linux / Other
----------|---------|-------|---------------
`EDIT_CFG_ICUUC_SONAME` | `icuuc.dll` | `libicucore.dylib` | `libicuuc.so`
`EDIT_CFG_ICUI18N_SONAME` | `icuin.dll` | `libicucore.dylib` | `libicui18n.so`
If your installation uses a different SONAME, please set the following environment variable at build time:
* `EDIT_CFG_ICUUC_SONAME`:
Expand Down
202 changes: 202 additions & 0 deletions assets/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
#!/bin/sh
# shellcheck shell=dash

set -eu

log() { printf '\033[1;34m==>\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33mwarning:\033[0m %s\n' "$*" >&2; }
die() { printf '\033[1;31merror:\033[0m %s\n' "$*" >&2; exit 1; }

usage() {
cat <<'EOF'
Usage: install.sh [--dev] [--system]
--dev Build from the main branch instead of the latest release
--system Install to /usr/local/bin (requires sudo)

Without --system, installs to ~/.local/bin.
EOF
exit 1
}

#### Parse arguments

dev=0
system=0
for arg in "$@"; do
case "$arg" in
--dev) dev=1 ;;
--system) system=1 ;;
-h|--help) usage ;;
*) usage ;;
esac
done

if [ "$system" = 1 ]; then
command -v sudo >/dev/null 2>&1 || die "sudo is required for --system installs."
fi

#### Check prerequisites

command -v cargo >/dev/null 2>&1 || die "cargo not found. Install Rust via rustup (https://rustup.rs) or your OS package manager."

if command -v curl >/dev/null 2>&1; then
download() { curl --proto '=https' --tlsv1.2 --retry 3 -fsSL -o "$1" "$2"; }
elif command -v wget >/dev/null 2>&1; then
download() { wget --https-only --secure-protocol=TLSv1_2 -qO "$1" "$2"; }
else
die "curl or wget not found. Install either via your OS package manager."
fi

tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT

#### Find ICU SONAME

icuuc_soname=""
icui18n_soname=""
icu_cpp_exports=""
icu_renaming_version=""

case "$(uname -s)" in
Darwin)
;;
*)
# Pick the best candidate SONAME
# Preference: libicuuc.so.78 > libicuuc.so > libicuuc.so.78.1
# (Symbols are usually suffixed with the major version, so that's preferred.)

icu_ranked_paths=$tmpdir/icu_ranked_paths

if command -v ldconfig >/dev/null 2>&1; then
ldconfig -p 2>/dev/null | grep -o '/.*libicuuc\.so.*$'
else
find /usr/local/lib /usr/local/lib64 /usr/lib /usr/lib64 /lib /lib64 -maxdepth 2 -name 'libicuuc.so*' 2>/dev/null
fi \
| while IFS= read -r icuuc_path; do
printf '%s %s\n' "${icuuc_path##*/}" "$icuuc_path"
done \
| sort -t. -k3,3n -k4,4n > "$icu_ranked_paths"

major_entry=$(grep -E '^libicuuc\.so\.[0-9]+ ' "$icu_ranked_paths" | tail -n1 || true)
bare_entry=$(grep -E '^libicuuc\.so ' "$icu_ranked_paths" | tail -n1 || true)
full_entry=$(grep -E '^libicuuc\.so\.[0-9]+\.[0-9]+ ' "$icu_ranked_paths" | tail -n1 || true)

if [ -n "$major_entry" ]; then icu_entry=$major_entry
elif [ -n "$bare_entry" ]; then icu_entry=$bare_entry
elif [ -n "$full_entry" ]; then icu_entry=$full_entry
else die "libicuuc not found. Install ICU via your OS package manager (e.g. libicu78, libicu, icu)."
fi

icuuc_soname=${icu_entry%% *}
icuuc_path=${icu_entry#* }
icui18n_path="${icuuc_path%/*}/libicui18n.so${icuuc_soname#libicuuc.so}"
[ -e "$icui18n_path" ] || die "libicui18n not found. Install ICU via your OS package manager (e.g. libicu78, libicu, icu)."
icui18n_soname=${icui18n_path##*/}

# Figure out the symbol naming scheme / renaming version

if command -v readelf >/dev/null 2>&1; then
icu_probe_symbol=$(readelf -Ws "$icuuc_path" 2>/dev/null | grep -Eo '_?u_errorName(_[0-9]+)?' | tail -n1 || true)
elif command -v nm >/dev/null 2>&1; then
icu_probe_symbol=$(nm -D "$icuuc_path" 2>/dev/null | grep -Eo '_?u_errorName(_[0-9]+)?' | tail -n1 || true)
else
icu_probe_symbol=
fi

case "$icu_probe_symbol" in
_u_errorName|_u_errorName_[0-9]*) icu_cpp_exports=true ;;
esac
case "$icu_probe_symbol" in
u_errorName_[0-9]*|_u_errorName_[0-9]*) icu_renaming_version=${icu_probe_symbol##*_} ;;
*) ;;
esac

log_renaming=""
log_cpp=""
if [ -n "$icu_renaming_version" ]; then
log_renaming=", renaming version $icu_renaming_version"
fi
if [ -n "$icu_cpp_exports" ]; then
log_cpp=", C++ symbol exports"
fi
log "Found $icuuc_soname, $icui18n_soname$log_renaming$log_cpp"
;;
esac

#### Download source

if [ "$dev" = 1 ]; then
log "Downloading main branch"
download "$tmpdir/edit.tar.gz" 'https://github.com/microsoft/edit/archive/refs/heads/main.tar.gz'
else
log "Fetching latest release tag"
download "$tmpdir/latest.json" 'https://api.github.com/repos/microsoft/edit/releases/latest'
tag=$(grep -oE '"tag_name": *"[^"]+"' "$tmpdir/latest.json" | grep -oE 'v[^"]+')
[ -n "$tag" ] || die "Could not determine latest release tag."
log "Latest release: $tag"
download "$tmpdir/edit.tar.gz" "https://github.com/microsoft/edit/archive/refs/tags/$tag.tar.gz"
fi

srcdir="$tmpdir/edit-src"
mkdir -p "$srcdir"
log "Extracting"
tar xf "$tmpdir/edit.tar.gz" -C "$srcdir" --strip-components=1

#### Build

log "Building"
[ -z "$icuuc_soname" ] || export EDIT_CFG_ICUUC_SONAME="$icuuc_soname"
[ -z "$icui18n_soname" ] || export EDIT_CFG_ICUI18N_SONAME="$icui18n_soname"
[ -z "$icu_cpp_exports" ] || export EDIT_CFG_ICU_CPP_EXPORTS="$icu_cpp_exports"
[ -z "$icu_renaming_version" ] || export EDIT_CFG_ICU_RENAMING_VERSION="$icu_renaming_version"

if rustup component list --installed 2>/dev/null | grep -q rust-src; then
(cd "$srcdir" && RUSTC_BOOTSTRAP=1 cargo build -p edit --release --config .cargo/release.toml)
else
warn "rust-src component not found; building without size optimizations"
(cd "$srcdir" && cargo build -p edit --release)
fi

bin="$srcdir/target/release/edit"
[ -x "$bin" ] || die "Build failed: binary not found."

#### Install

if [ "$system" = 1 ]; then
dest=/usr/local/bin
run="sudo"
else
dest="$HOME/.local/bin"
run=""
fi

log "Installing to $dest"
$run mkdir -p "$dest"
$run cp "$bin" "$dest/msedit"
$run chmod 755 "$dest/msedit"
if [ ! -e "$dest/edit" ] || [ "$(readlink "$dest/edit" 2>/dev/null)" = "msedit" ]; then
$run ln -sf msedit "$dest/edit"
edit_linked=1
else
edit_linked=0
fi

#### Summary

case ":$PATH:" in
*":$dest:"*)
if [ "$edit_linked" = 1 ]; then
echo "✅ Done. Run 'edit' or 'msedit' to start."
else
echo "✅ Done. Run 'msedit' to start."
fi
;;
*)
echo "⚠️ Done. $dest is not in PATH; you may need to add it."
if [ "$edit_linked" = 1 ]; then
echo "Run '$dest/edit' or '$dest/msedit' to start."
else
echo "Run '$dest/msedit' to start."
fi
;;
esac
Loading