diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..e2ccc58 --- /dev/null +++ b/.envrc @@ -0,0 +1,149 @@ +# Usage: use_nix [...] +# +# Load environment variables from `nix-shell`. +# If you have a `default.nix` or `shell.nix` one of these will be used and +# the derived environment will be stored at ./.direnv/env- +# and symlink to it will be created at ./.direnv/default. +# Dependencies are added to the GC roots, such that the environment remains persistent. +# +# The resulting environment is cached for better performance. +# +# To trigger switch to a different environment: +# `rm -f .direnv/default` +# +# To derive a new environment: +# `rm -rf .direnv/env-$(md5sum {shell,default}.nix 2> /dev/null | cut -c -32)` +# +# To remove cache: +# `rm -f .direnv/dump-*` +# +# To remove all environments: +# `rm -rf .direnv/env-*` +# +# To remove only old environments: +# `find .direnv -name 'env-*' -and -not -name `readlink .direnv/default` -exec rm -rf {} +` +# + +set -eo pipefail + +use_nix() { + # define all local variables + local shell f env_hash dir default wd drv dump path_backup + local files_to_watch=() + + while getopts ":s:w:" opt; do + case "${opt}" in + s) + shell="${OPTARG}" + files_to_watch=("${files_to_watch[@]}" "${shell}") + ;; + w) + files_to_watch=("${files_to_watch[@]}" "${OPTARG}") + ;; + :) + >&2 echo "Invalid option: $OPTARG requires an argument" + ;; + \?) + >&2 echo "Invalid option: $OPTARG" + exit 1 + ;; + esac + done + shift $((OPTIND -1)) + + if [[ -z "${shell}" ]]; then + >&2 echo "ERR: no shell was given" + exit 1 + fi + + for f in "${files_to_watch[@]}"; do + if ! [[ -f "${f}" ]]; then + >&2 echo "cannot watch file ${f} because it does not exist" + exit 1 + fi + done + + # compute the hash of all the files that makes up the development environment + env_hash="$(hashContents "${files_to_watch[@]}")" + + dir="$(direnv_layout_dir)" + default="${dir}/default" + if [[ ! -L "${default}" ]] || [[ ! -d $(readlink "${default}") ]]; then + wd="${dir}/env-${env_hash}" + mkdir -p "${wd}" + + drv="${wd}/env.drv" + if [[ ! -f "${drv}" ]]; then + log_status "use nix: deriving new environment" + IN_NIX_SHELL=1 nix-instantiate --add-root "${drv}" --indirect "${shell}" > /dev/null + nix-store -r $(nix-store --query --references "${drv}") --add-root "${wd}/dep" --indirect > /dev/null + fi + + rm -f "${default}" + ln -s $(basename "${wd}") "${default}" + fi + + drv=$(readlink "${default}/env.drv") + dump="${dir}/dump-$(hashFile ".envrc")-$(hashFile ${drv})" + + if [[ ! -f "${dump}" ]] || [[ "${XDG_CONFIG_DIR}/direnv/direnvrc" -nt "${dump}" ]]; then + log_status "use nix: updating cache" + + old=$(find "${dir}" -name 'dump-*') + nix-shell --pure "${drv}" --show-trace --run "$(join_args "$direnv" dump bash)" > "${dump}" + rm -f ${old} + fi + + # evaluate the dump created by nix-shell earlier, but have to merge the PATH + # with the current PATH + # NOTE: we eval the dump here as opposed to direnv_load it because we don't + # want to persist environment variables coming from the shell at the time of + # the dump. See https://github.com/direnv/direnv/issues/405 for context. + path_backup="${PATH}" + eval $(cat "${dump}") + export PATH="${PATH}:${path_backup}" + + for f in "${files_to_watch[@]}"; do + watch_file "${f}" + done +} + +hashContents() { + if has md5sum; then + cat "${@}" | md5sum | cut -c -32 + elif has md5; then + cat "${@}" | md5 -q + fi +} + +hashFile() { + if has md5sum; then + md5sum "${@}" | cut -c -32 + elif has md5; then + md5 -q "${@}" + fi +} + +fail() { + log_error "${@}" + exit 1 +} + +validateVersion() { + local version="$("${direnv}" version)" + local major="$(echo "${version}" | cut -d. -f1)" + local minor="$(echo "${version}" | cut -d. -f2)" + local patch="$(echo "${version}" | cut -d. -f3)" + + if [[ "${major}" -gt 2 ]]; then return 0; fi + if [[ "${major}" -eq 2 ]] && [[ "${minor}" -gt 18 ]]; then return 0; fi + if [[ "${major}" -eq 2 ]] && [[ "${minor}" -eq 18 ]] && [[ "${patch}" -ge 2 ]]; then return 0; fi + return 1 +} + +if ! validateVersion; then + echo "This .envrc requires direnv version 2.18.2 or above." + exit 1 +fi + +use_nix -s shell.nix diff --git a/.gitignore b/.gitignore index a9c5802..d11e157 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ docs/build __pycache__/ *.pyc +.direnv/ diff --git a/Cargo.toml b/Cargo.toml index 01f49cb..0713920 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "elba" description = "elba is a package manager for Idris" -authors = ["David Cao "] +authors = ["David Cao "] version = "0.3.0" license = "MIT" edition = "2018" diff --git a/docs/src/index.rst b/docs/src/index.rst index 5f5b333..1eb25c1 100644 --- a/docs/src/index.rst +++ b/docs/src/index.rst @@ -8,8 +8,7 @@ to be a mostly comprehensive guide on actually using it. :maxdepth: 2 :caption: Usage - usage/getting_started - usage/configuration + usage/quick_start usage/installing usage/custom_subcommands usage/publishing @@ -18,9 +17,11 @@ to be a mostly comprehensive guide on actually using it. :maxdepth: 2 :caption: Reference + reference/configuration reference/resolutions reference/manifest reference/indices + reference/registries reference/dependencies reference/cache diff --git a/docs/src/usage/configuration.rst b/docs/src/reference/configuration.rst similarity index 100% rename from docs/src/usage/configuration.rst rename to docs/src/reference/configuration.rst diff --git a/docs/src/reference/indices.rst b/docs/src/reference/indices.rst index 24923c3..3ae2854 100644 --- a/docs/src/reference/indices.rst +++ b/docs/src/reference/indices.rst @@ -26,7 +26,7 @@ direct resolutions as their actual location. This makes elba’s package indices extremely powerful as a consequence. Users can have their packages appear in indices by uploading them to -:doc:`index backends <./backends>`. +their corresponding :doc:`registries <./registries>`. Index Resolutions ~~~~~~~~~~~~~~~~~ @@ -97,7 +97,7 @@ index which the packages of this index need to build properly must be specified in this field, or else package building will fail during dependency resolution. -An additional key, ``backend``, should be the url of the backend API. +An additional key, ``registry``, should be the url of the registry API. Metadata structure ~~~~~~~~~~~~~~~~~~ diff --git a/docs/src/reference/registries.rst b/docs/src/reference/registries.rst new file mode 100644 index 0000000..472949e --- /dev/null +++ b/docs/src/reference/registries.rst @@ -0,0 +1,49 @@ +Registries +========== + +Where `indices <./indices>` can be thought of as the "read-only" part of +a package repository, providing information about packages and nothing +more, a **registry** is a package server which serves the actual package +files and allows users to upload and yank packages from them. + +All registries are tied to indices - a registry must have a +corresponding package index (though the opposite isn't necessarily +true). Package registries can be specified as URLs in the configuration +of an index with the following syntax: + +.. code-block:: toml + [index] + secure = false + registry = "https://api.elba.pub" + +API v1 Endpoints +---------------- + +It's assumed that all package registries have some sort of auth system +centered around a user token which allows the registry to authenticate +and authorize different users. elba users can log in to a registry +using the ``elba login `` command, where token is the auth token +provided to them by the registry. + +In order to function with elba, package registries must support two +basic operations: + +- **Package publishing**: package registries should be able to have + packages uploaded to them at the PUT endpoint ``/api/v1/publish``. + The body of this request will be the package in archived (tar.gz) + form, and the auth token will be provided as a query parameter + ``token``. + +- **Package yanking**: in order to prevent a left-pad-esque scenario, + the public interface of a package registry prohibits package + deletion; instead, packages can be yanked, which means that future + packages will be unable to depend on said package or package version. + A package ``group/name|version`` can be yanked at the PATCH endpoint + ``/api/v1/group/name/version/yank``. This endpoint should accept a + boolean query parameter ``yanked`` (usually set to ``true``) and a + query parameter ``token``. + +Currently, these are the two endpoints which elba needs to function. +However, the full list of endpoints is much longer than this, and can +be found in the `source code of the reference elba registry +`__. diff --git a/docs/src/usage/installing_idris.rst b/docs/src/usage/installing_idris.rst new file mode 100644 index 0000000..9ebbb85 --- /dev/null +++ b/docs/src/usage/installing_idris.rst @@ -0,0 +1,4 @@ +Installing Idris +================ + +This chapter will go over installation of the Idris toolchain, diff --git a/docs/src/usage/publishing.rst b/docs/src/usage/publishing.rst index 1273b03..c646fc5 100644 --- a/docs/src/usage/publishing.rst +++ b/docs/src/usage/publishing.rst @@ -1,4 +1,4 @@ -Publishing to an Index -=============================== +Publishing Packages +=================== -TODO: Rewriting for 0.3.0... +TODO: fix for 0.3.0 diff --git a/docs/src/usage/getting_started.rst b/docs/src/usage/quick_start.rst similarity index 95% rename from docs/src/usage/getting_started.rst rename to docs/src/usage/quick_start.rst index c107119..8a1a432 100644 --- a/docs/src/usage/getting_started.rst +++ b/docs/src/usage/quick_start.rst @@ -1,5 +1,5 @@ -Getting Started -=============== +Quick Start +=========== This section is for getting up-to-speed with elba as fast as possible, covering getting elba installed on your machine in the first place and @@ -9,6 +9,11 @@ By the end of this chapter, you should have a basic elba installation up-and-running, as well as a general overview of how to use elba for day-to-day Idris development. +We will assume that you already have the Idris toolchain installed. If +you don't, there are instructions available on the +`Idris `__ and +`Blodwen `__ websites. + Installation ------------ @@ -68,15 +73,8 @@ After that’s done, download elba’s source code and install it: Remember to add ``~/.elba/bin`` to your PATH to be able to run elba-installed packages. -Quick Start ------------ - -This section intends to be a whirlwind tour of all the functionality -available with elba. For more information on each step, refer to either -the Usage or Reference chapters. - Creating a package -~~~~~~~~~~~~~~~~~~ +------------------ Creating a package is easy with elba: all you need is a package name. Note that names in elba are special in that they are *always @@ -104,7 +102,7 @@ Regardless of which target is chosen, an ``elba.toml`` manifest file will also be generated. Initializing a pre-existing package -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you already have an Idris project and want to turn it into an elba project, use the ``elba init`` command instead; it follows the exact @@ -112,7 +110,7 @@ same syntax as ``elba new`` and is functionally identical, but uses the current directory instead of making a new one. Adding dependencies -~~~~~~~~~~~~~~~~~~~ +------------------- Now that a new package has been created, you can start to add packages as part of your dependencies. A package can originate from one of three @@ -142,7 +140,7 @@ At this point, you can add whatever files you want and import anything from your dependencies. Targets -~~~~~~~ +------- The manifest also allows you to specify which targets you want to have built for your package. There are three types of targets: @@ -188,7 +186,7 @@ built for your package. There are three types of targets: binary succeeds upon execution if it returns exit code 0. Building a package -~~~~~~~~~~~~~~~~~~ +------------------ …can be accomplished with the command: diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..213c3ad --- /dev/null +++ b/shell.nix @@ -0,0 +1,19 @@ +let + moz_overlay = import (builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz); + nixpkgs = import { overlays = [ moz_overlay ]; }; + rustStableChannel = nixpkgs.latest.rustChannels.stable.rust.override { + extensions = [ + "rust-src" + "rls-preview" + "clippy-preview" + "rustfmt-preview" + ]; + }; +in +with nixpkgs; + stdenv.mkDerivation { + name = "moz_overlay_shell"; + buildInputs = [ + openssl pkgconfig rustStableChannel + ]; + } diff --git a/src/lib/remote/registry.rs b/src/lib/remote/registry.rs index 99c885c..18c3612 100644 --- a/src/lib/remote/registry.rs +++ b/src/lib/remote/registry.rs @@ -1,36 +1,14 @@ //! Package repositories that can be published/yanked to. //! elba reads from indices and writes to repos. -use crate::{package::Name, remote::Indices, util::errors::Res}; +use crate::{package::Name, util::errors::Res}; use failure::{format_err, Error, ResultExt}; use reqwest::Client; use semver::Version; use serde::{de, ser}; use serde_derive::{Deserialize, Serialize}; use std::{fmt, fs::File, str::FromStr, time::Duration}; -use url::{ - percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}, - Url, -}; - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct SearchResponse { - pub packages: Vec, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct SearchPkg { - pub group: String, - #[serde(rename = "package")] - pub name: String, -} - -#[derive(Debug, Clone)] -pub struct SearchVersioned { - pub group: String, - pub name: String, - pub version: Version, -} +use url::Url; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Registry { @@ -133,40 +111,6 @@ impl Registry { } } - pub fn search(&self, indices: &Indices, query: &str) -> Res> { - let client = Client::builder().timeout(Duration::from_secs(10)).build()?; - // Encode the string so that it's safe for a url - let query = utf8_percent_encode(query, DEFAULT_ENCODE_SET).collect::(); - let mut resp = client - .get(self.url.join("api/v1/packages/search").unwrap()) - .query(&[("q", query)]) - .send()?; - - if resp.status().is_success() { - let sr = resp.json::>()?; - - let packages = sr - .packages - .into_iter() - .map(|x| { - let n = Name::new(x.group.clone(), x.name.clone()).unwrap(); - let sum = indices.select_by_spec(&n.into())?; - Ok(SearchVersioned { - group: x.group, - name: x.name, - version: sum.version, - }) - }) - .collect::>()?; - - let ns = SearchResponse { packages }; - - Ok(ns) - } else { - Err(format_err!("{} {}", resp.status(), resp.text()?))? - } - } - pub fn retrieve_url(&self, name: &Name, version: &Version) -> Url { self.url .join(&format!(