diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..760d011a6 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +github: [mosteo] # Up to 4 GitHub Sponsors-enabled usernames; e.g., [user1, user2] +polar: alire-project +polar: mosteo +open_collective: alire diff --git a/.github/workflows/ci-appimage.yml b/.github/workflows/ci-appimage.yml index 023b05bbb..f7e3ac4e9 100644 --- a/.github/workflows/ci-appimage.yml +++ b/.github/workflows/ci-appimage.yml @@ -48,7 +48,7 @@ jobs: - name: Upload logs (if failed) if: failure() - uses: actions/upload-artifact@master + uses: actions/upload-artifact@v4 with: name: e3-log-linux.zip path: testsuite/out @@ -114,7 +114,7 @@ jobs: # regular PRs for easy testing. - name: Upload as artifact (when not a release) if: (github.event_name != 'release') - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: alr-${{ steps.get_ref.outputs.short_sha }}-x86_64.AppImage.zip path: alr.AppImage diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index a01ad2d05..09e3ed1e2 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -32,8 +32,18 @@ jobs: with: submodules: true + - name: OS information for ${{ matrix.tag }} + uses: mosteo-actions/docker-run@v2 + with: + image: ghcr.io/alire-project/docker/gnat:${{matrix.tag}} + command: | + lsb_release -a || \ + cat /etc/os-release || \ + cat /etc/system-release || \ + echo "No lsb_release information" + - name: Run test script (${{ matrix.tag }}) - uses: mosteo-actions/docker-run@v1 + uses: mosteo-actions/docker-run@v2 with: image: ghcr.io/alire-project/docker/gnat:${{matrix.tag}} command: scripts/ci-github.sh @@ -41,7 +51,7 @@ jobs: - name: Upload logs (if failed) if: failure() - uses: actions/upload-artifact@master + uses: actions/upload-artifact@v4 with: name: e3-log-docker-${{ matrix.tag }}.zip path: testsuite/out diff --git a/.github/workflows/ci-linux.yml b/.github/workflows/ci-linux.yml index 7992d2199..e14e64d9a 100644 --- a/.github/workflows/ci-linux.yml +++ b/.github/workflows/ci-linux.yml @@ -48,7 +48,7 @@ jobs: INDEX: "" - name: Upload binaries - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: alr-bin-linux.zip path: | @@ -57,7 +57,7 @@ jobs: - name: Upload logs (if failed) if: failure() - uses: actions/upload-artifact@master + uses: actions/upload-artifact@v4 with: name: e3-log-linux.zip path: testsuite/out diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml index 6903a4462..e87ea3571 100644 --- a/.github/workflows/ci-macos.yml +++ b/.github/workflows/ci-macos.yml @@ -62,7 +62,7 @@ jobs: INDEX: "" - name: Upload binaries - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: alr-bin-${{ env.ARCH }}-macos.zip path: | @@ -71,7 +71,7 @@ jobs: - name: Upload logs (if failed) if: failure() - uses: actions/upload-artifact@master + uses: actions/upload-artifact@v4 with: name: testsuite-log-macos.zip path: testsuite/out diff --git a/.github/workflows/ci-unsupported.yml b/.github/workflows/ci-unsupported.yml index 5d19dc271..73252f42f 100644 --- a/.github/workflows/ci-unsupported.yml +++ b/.github/workflows/ci-unsupported.yml @@ -43,7 +43,7 @@ jobs: - name: Upload logs (if failed) if: failure() - uses: actions/upload-artifact@master + uses: actions/upload-artifact@v4 with: name: e3-log-unsupported.zip path: testsuite/out diff --git a/.github/workflows/ci-windows.yml b/.github/workflows/ci-windows.yml index a909227d7..1193bbbad 100644 --- a/.github/workflows/ci-windows.yml +++ b/.github/workflows/ci-windows.yml @@ -89,26 +89,26 @@ jobs: ALR_INSTALL_OS: ${{ runner.os }} - name: Upload installer - uses: actions/upload-artifact@main + uses: actions/upload-artifact@v4 with: name: installer-release-package path: scripts/installer/alire-*.exe - name: Upload zip archive - uses: actions/upload-artifact@main + uses: actions/upload-artifact@v4 with: name: zip-release-package path: scripts/installer/alire-*.zip - name: Upload tar archive - uses: actions/upload-artifact@main + uses: actions/upload-artifact@v4 with: name: tar-release-package path: scripts/installer/alire-*.tar.xz - name: Upload logs (if failed) if: failure() - uses: actions/upload-artifact@master + uses: actions/upload-artifact@v4 with: name: testsuite-log-windows.zip path: testsuite/out diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 502f20e7b..d44685008 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -33,7 +33,9 @@ jobs: submodules: true - name: Install FSF toolchain - uses: alire-project/setup-alire@v3 + uses: alire-project/alr-install@v1 + with: + crates: gnat_native gprbuild - name: Replace toolchain with aarch64 if: ${{ runner.arch == 'ARM64' }} @@ -56,13 +58,13 @@ jobs: - name: Upload logs (if failed) if: failure() - uses: actions/upload-artifact@master + uses: actions/upload-artifact@v4 with: name: e3-log-linux.zip path: testsuite/out - name: Upload artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: alr-bin-${{ matrix.os }}.zip path: | @@ -89,7 +91,6 @@ jobs: - name: Package binaries (macOS/x64) if: startsWith(matrix.os, 'macos') && runner.arch == 'X64' run: zip alr-nightly-bin-x86_64-macos.zip bin/alr* LICENSE.txt alr-*.txt - - name: Package binaries (macOS/arm64) if: startsWith(matrix.os, 'macos') && runner.arch == 'ARM64' run: zip alr-nightly-bin-aarch64-macos.zip bin/alr* LICENSE.txt alr-*.txt diff --git a/.gitmodules b/.gitmodules index fc56fd8ee..3054b0373 100644 --- a/.gitmodules +++ b/.gitmodules @@ -60,3 +60,12 @@ [submodule "deps/dirty_booleans"] path = deps/dirty_booleans url = https://github.com/mosteo/dirty_booleans +[submodule "deps/den"] + path = deps/den + url = https://github.com/mosteo/den +[submodule "deps/cstrings"] + path = deps/cstrings + url = https://github.com/mosteo/cstrings +[submodule "deps/lml"] + path = deps/lml + url = https://github.com/mosteo/lml_ada.git diff --git a/BREAKING.md b/BREAKING.md index c23c39804..4809195fb 100644 --- a/BREAKING.md +++ b/BREAKING.md @@ -1,9 +1,19 @@ Log of breaking changes in index or alr. -### alr 2.0.0 + index 1.2.2 +### alr 3.0.0 + index 1.3.0 + +- alr: removed `ALR_CONFIG` environment variable. +- alr: removed `alr config` command. + +### alr 2.0.0 + index 1.3.0 + +- index:`binary` property required in binary origins. +- index: Paths in `[environment]` must be portable (using forward slashes). +- alr: removed `alr toolchain`'s `--install, --uninstall, --install-dir`. +- alr: deprecated (but still working) `ALR_CONFIG`, `alr config`. ### alr 1.2.2 + index 1.2.1 -- Unable to load externals containing regex special characters in the system +- alr: unable to load externals containing regex special characters in the system package name (fixed in #1545). -- Paths in `[environment]` are not converted to the native platform convention. +- alr: paths in `[environment]` are not converted to the native platform convention. diff --git a/README.md b/README.md index 5a46ff81c..d0e6223dc 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![MacOS CI](https://github.com/alire-project/alire/workflows/CI%20macOS/badge.svg)](https://github.com/alire-project/alire/actions) [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/ada-lang/Alire) [![Gitpod ready](https://img.shields.io/badge/Gitpod-ready-908a85?logo=gitpod)](https://gitpod.io/#https://github.com/alire-project/alire) +[![Funding](https://polar.sh/embed/seeks-funding-shield.svg?org=alire-project)](https://polar.sh/alire-project) # ALR # @@ -20,7 +21,7 @@ https://alire.ada.dev/ ## TL;DR ## -Available for Linux/macOS/Windows/FreeBSD. +Available for Linux/macOS/Windows/FreeBSD/OpenBSD. Download the latest stable version from the [Releases](https://github.com/alire-project/alire/releases) page. See the [Getting Started](doc/getting-started.md) guide for binary downloads. @@ -34,7 +35,7 @@ See the [Getting Started](doc/getting-started.md) guide. The build process of `alr` is straighforward and depends only on a recent GNAT Ada 2012 compiler. All dependencies are included as submodules. A project file (`alr_env.gpr`) is provided to drive the build with all necessary configuration (which is also valid for editing with GNAT Studio). -The ALIRE_OS environment variable must be set to the OS for which `alr` is being build, taking one of the values in `freebsd`, `linux`, `macos`, `windows`. +The ALIRE_OS environment variable must be set to the OS for which `alr` is being build, taking one of the values in `freebsd`, `openbsd`, `linux`, `macos`, `windows`. Follow these steps: @@ -42,7 +43,7 @@ Follow these steps: 1. Enter the cloned repository folder. 1. Build the executable: * if you have Bash on your system: `dev/build.sh` - * if you don't have Bash on your system: `ALIRE_OS= gprbuild -j0 -p -P alr_env` + * if you don't have Bash on your system: `ALIRE_OS= gprbuild -j0 -p -P alr_env` The binary will be found at `bin/alr`. You can run `alr version` to see version and diagnostics information. @@ -80,7 +81,7 @@ environment `alr` is using with `alr printenv`. ## Supported platforms ## -Alire can be built on Linux, macOS, Windows, and FreeBSD. +Alire can be built on Linux, macOS, Windows, FreeBSD, and OpenBSD. Alire requires a recent Ada 2012 compiler. In practice, this currently means the latest [GNAT Community](https://www.adacore.com/download) or a somewhat diff --git a/RELEASING.md b/RELEASING.md index e59618a05..fe938343b 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -2,8 +2,10 @@ 1. [ ] Update Msys2 installer at https://github.com/msys2/msys2-installer/releases/ 1. [ ] Run local-only tests (`/testsuite/run-dev.sh`) -1. [ ] Update version in `Alire.Version`. -1. [ ] Update version in `alire.toml`. +1. [ ] Update versions + - `Alire.Version` + - `alire.toml` + - `user-changes.md` 1. [ ] Create test release in own fork. - To verify builds succeed. - As the Windows build can rarely fail, this provides a backup .exe diff --git a/alire.gpr b/alire.gpr index 3490e92de..052067841 100644 --- a/alire.gpr +++ b/alire.gpr @@ -3,10 +3,13 @@ with "ada_toml"; with "alire_common"; with "ajunitgen"; with "ansiada"; +with "c_strings"; with "clic"; +with "den"; with "dirty_booleans"; with "diskflags"; with "gnatcoll"; +with "lml"; with "minirest"; with "optional"; with "semantic_versioning"; @@ -28,6 +31,7 @@ library project Alire is case Alire_Common.Host_Os is when "freebsd" => Src_Dirs := Src_Dirs & ("src/alire/os_freebsd"); + when "openbsd" => Src_Dirs := Src_Dirs & ("src/alire/os_openbsd"); when "linux" => Src_Dirs := Src_Dirs & ("src/alire/os_linux"); when "macos" => Src_Dirs := Src_Dirs & ("src/alire/os_macos"); when "windows" => Src_Dirs := Src_Dirs & ("src/alire/os_windows"); @@ -38,6 +42,9 @@ library project Alire is when "freebsd" => for body ("Alire.Platforms.Current") use "alire-platforms-current__freebsd.adb"; for body ("Alire.Platforms.Folders") use "alire-platforms-folders__freebsd.adb"; + when "openbsd" => + for body ("Alire.Platforms.Current") use "alire-platforms-current__openbsd.adb"; + for body ("Alire.Platforms.Folders") use "alire-platforms-folders__openbsd.adb"; when "linux" => for body ("Alire.Platforms.Current") use "alire-platforms-current__linux.adb"; for body ("Alire.Platforms.Folders") use "alire-platforms-folders__linux.adb"; diff --git a/alire.toml b/alire.toml index fad6eb214..b853e5fa8 100644 --- a/alire.toml +++ b/alire.toml @@ -1,7 +1,7 @@ name = "alr" description = "Command-line tool from the Alire project" -version = "2.0.2" +version = "2.1-dev" authors = ["Alejandro R. Mosteo", "Fabien Chouteau", "Pierre-Marie de Rodat"] maintainers = ["alejandro@mosteo.com", "chouteau@adacore.com"] @@ -18,11 +18,14 @@ executables = ["alr"] aaa = "~0.3.0" ada_toml = "~0.3" ajunitgen = "^1.0.1" -ansiada = "^1.0" +ansiada = "^1.1" +c_strings = "^1.0" clic = "~0.3" +den = "~0.1" dirty_booleans = "~0.1" diskflags = "~0.1" gnatcoll = "^21" +lml = "~0.1" minirest = "~0.3" optional = "~0.1" semantic_versioning = "^3.0" @@ -40,6 +43,7 @@ CLIC_LIBRARY_TYPE="static" # Building alr requires the explicit setting of this variable [gpr-set-externals."case(os)"] freebsd = { ALIRE_OS = "freebsd" } +openbsd = { ALIRE_OS = "openbsd" } linux = { ALIRE_OS = "linux" } macos = { ALIRE_OS = "macos" } windows = { ALIRE_OS = "windows" } @@ -47,18 +51,31 @@ windows = { ALIRE_OS = "windows" } # Some dependencies require precise versions during the development cycle: [[pins]] + [pins.aaa] url = "https://github.com/mosteo/aaa" -commit = "dff61d2615cc6332fa6205267bae19b4d044b9da" +commit = "ddfeffe2d6c8f9d19161df7b31d16d37bef4ba71" [pins.ada_toml] url = "https://github.com/mosteo/ada-toml" commit = "da4e59c382ceb0de6733d571ecbab7ea4919b33d" +[pins.ansiada] +url = "https://github.com/mosteo/ansi-ada" +commit = "0772e48d3e1f640829d142745a36b37edcd5470b" + +[pins.c_strings] +url = "https://github.com/mosteo/cstrings" +commit = "e4d58ad90bf32bc44304197e5906a519f5a9a7bf" + [pins.clic] url = "https://github.com/alire-project/clic" commit = "56bbdc008e16996b6f76e443fd0165a240de1b13" +[pins.den] +url = "https://github.com/mosteo/den" +commit = "b12e8461bf41e2cfe199c8196b45fa4fc213a6aa" + [pins.dirty_booleans] url = "https://github.com/mosteo/dirty_booleans" commit = "05c40d88ecfe109e575ec8b21dd6ffa2e61df1dc" @@ -71,6 +88,10 @@ commit = "60729edf31816aca0036b13b2794c39a9bd0172e" url = "https://github.com/alire-project/gnatcoll-core.git" commit = "4e663b87a028252e7e074f054f8f453661397166" +[pins.lml] +url = "https://github.com/mosteo/lml_ada.git" +commit = "ae156ef82a2fedb7e28bb4dcaeb3d5c0a2e046ec" + [pins.minirest] url = "https://github.com/mosteo/minirest.git" commit = "9a9c660f9c6f27f5ef75417e7fac7061dff14d78" diff --git a/alire_common.gpr b/alire_common.gpr index 39dabb983..fe88657b9 100644 --- a/alire_common.gpr +++ b/alire_common.gpr @@ -4,6 +4,7 @@ abstract project Alire_Common is type Host_OSes is ("linux", "freebsd", + "openbsd", "macos", "windows"); diff --git a/alr.gpr b/alr.gpr index f821cd135..00620f5d4 100644 --- a/alr.gpr +++ b/alr.gpr @@ -14,6 +14,7 @@ project Alr is case Alire_Common.Host_Os is when "freebsd" => Src_Dirs := Src_Dirs & ("src/alr/os_linux"); + when "openbsd" => Src_Dirs := Src_Dirs & ("src/alr/os_linux"); when "linux" => Src_Dirs := Src_Dirs & ("src/alr/os_linux"); when "macos" => Src_Dirs := Src_Dirs & ("src/alr/os_macos"); when "windows" => Src_Dirs := Src_Dirs & ("src/alr/os_windows"); diff --git a/alr_env.gpr b/alr_env.gpr index 0fbb9ba12..c9a6a6b3e 100644 --- a/alr_env.gpr +++ b/alr_env.gpr @@ -14,9 +14,12 @@ aggregate project Alr_Env is "deps/ajunitgen", "deps/ansi", "deps/clic", + "deps/cstrings", + "deps/den", "deps/dirty_booleans", "deps/diskflags", "deps/gnatcoll-slim", + "deps/lml", "deps/minirest", "deps/optional", "deps/semantic_versioning", @@ -36,6 +39,7 @@ aggregate project Alr_Env is case Alire_Common.Host_Os is when "freebsd" => for External ("GNATCOLL_OS") use "unix"; + when "openbsd" => for External ("GNATCOLL_OS") use "unix"; when "linux" => for External ("GNATCOLL_OS") use "unix"; when "macos" => for External ("GNATCOLL_OS") use "osx"; when "windows" => for External ("GNATCOLL_OS") use "windows"; diff --git a/cleaner.yml b/cleaner.yml new file mode 100644 index 000000000..a4ceee694 --- /dev/null +++ b/cleaner.yml @@ -0,0 +1,19 @@ +name: 'Close stale PRs' +on: + workflow_dispatch: + schedule: + - cron: '11 1 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - uses: actions/stale@v7 + with: + debug-only : false # Set to true to work in dry-run mode + stale-pr-message: 'This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 30 days.' + close-pr-message: 'This PR was closed because it has been stalled for 90 days with no activity.' + days-before-stale: 60 + days-before-close: 30 diff --git a/deps/aaa b/deps/aaa index dff61d261..ddfeffe2d 160000 --- a/deps/aaa +++ b/deps/aaa @@ -1 +1 @@ -Subproject commit dff61d2615cc6332fa6205267bae19b4d044b9da +Subproject commit ddfeffe2d6c8f9d19161df7b31d16d37bef4ba71 diff --git a/deps/ansi b/deps/ansi index dc770a5a6..0772e48d3 160000 --- a/deps/ansi +++ b/deps/ansi @@ -1 +1 @@ -Subproject commit dc770a5a6cdaad8668c32b0cd4625a7d648f8ca2 +Subproject commit 0772e48d3e1f640829d142745a36b37edcd5470b diff --git a/deps/cstrings b/deps/cstrings new file mode 160000 index 000000000..e4d58ad90 --- /dev/null +++ b/deps/cstrings @@ -0,0 +1 @@ +Subproject commit e4d58ad90bf32bc44304197e5906a519f5a9a7bf diff --git a/deps/den b/deps/den new file mode 160000 index 000000000..b12e8461b --- /dev/null +++ b/deps/den @@ -0,0 +1 @@ +Subproject commit b12e8461bf41e2cfe199c8196b45fa4fc213a6aa diff --git a/deps/lml b/deps/lml new file mode 160000 index 000000000..ae156ef82 --- /dev/null +++ b/deps/lml @@ -0,0 +1 @@ +Subproject commit ae156ef82a2fedb7e28bb4dcaeb3d5c0a2e046ec diff --git a/dev/clean.sh b/dev/clean.sh new file mode 100755 index 000000000..9da51bfb0 --- /dev/null +++ b/dev/clean.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +# Import reusable bits +pushd $( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) > /dev/null || exit 1 + . functions.sh +popd > /dev/null || exit 1 + +export ALIRE_OS=$(get_OS) + +gprclean -f -r -Palr_env.gpr diff --git a/dev/edit.sh b/dev/edit.sh index 66ca7ca90..a429fe80e 100755 --- a/dev/edit.sh +++ b/dev/edit.sh @@ -1 +1,8 @@ +# Import reusable bits +pushd $( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) > /dev/null || exit 1 + . functions.sh +popd > /dev/null || exit 1 + +export ALIRE_OS=$(get_OS) + gnatstudio -P alr_env & >/dev/null 2>&1 diff --git a/dev/functions.sh b/dev/functions.sh index 9ded1e895..d76314503 100755 --- a/dev/functions.sh +++ b/dev/functions.sh @@ -13,6 +13,9 @@ function guess_OS() { "freebsd") echo freebsd ;; + "openbsd") + echo openbsd + ;; "darwin"*) # varies with versions: darwin18, darwin19, etc. echo macos ;; @@ -37,6 +40,9 @@ function get_OS() { "FreeBSD") echo freebsd ;; + "OpenBSD") + echo openbsd + ;; "Darwin") echo macos ;; diff --git a/doc/catalog-format-spec.md b/doc/catalog-format-spec.md index 196aa5ca3..2df346ffa 100644 --- a/doc/catalog-format-spec.md +++ b/doc/catalog-format-spec.md @@ -182,14 +182,18 @@ static, i.e. they cannot depend on the context. "Bob For Instance "] ``` - - `maintainers-logins`: mandatory (for indexing) array of strings. Flat - list of github login usernames used by the maintainers of the crate. This - information is used to authorize crate modifications. For instance: + - `maintainers-logins`: optional array of non-empty strings. + For crates submitted to the community index, this is a mandatory flat list of + the GitHub login usernames authorized to modify the crate. + For instance: ```toml maintainers-logins = ["alicehacks", "bobcoder"] ``` + Private indexes may use whichever logins are appropriate for their + hosting arrangement, or none at all. + - `licenses`: mandatory (for indexing) string. A valid [SPDX expression](https://spdx.org/licenses/). Custom license identifiers are accepted with the format: `custom-[0-9a-zA-Z.-]+` @@ -878,8 +882,8 @@ available.'case(toolchain)'.user = false ## Parameters - - `os`: name of the OS. Currently supported values are: `freebsd`, `linux`, - `macos`, `windows`, and `os-unknown`. + - `os`: name of the OS. Currently supported values are: `freebsd`, `openbsd`, + `linux`, `macos`, `windows`, and `os-unknown`. - `distribution`: name of the Linux distribution or name of the software distribution platform if running on a different OS. Currently supported diff --git a/doc/publishing.md b/doc/publishing.md index 8ec90c3c9..23ab40ca7 100644 --- a/doc/publishing.md +++ b/doc/publishing.md @@ -313,15 +313,37 @@ This will be shown as: ## Publishing to a local/private index -Having a local index may be useful sometimes, be it for local testing, or for -private crates not intended for publication. - -There is no practical difference between the community index that is cloned -locally and a private local index stored on disk. Hence, after obtaining the -manifest file with `alr publish`, it is a matter of placing it at the expected -location within the index: `/path/to/index/cr/crate_name/crate_name-x.x.x.toml` - -If the crate being published locally contains `"provides"` definitions, it is -necessary to call `alr index --update-all` once to ensure it is properly used -by the dependency solver. This is only necessary for the first release in a -crate that uses the `"provides"` feature. +Having a local or private index may be useful sometimes, be it for local +testing, or for private crates not intended for publication. + +There is no practical difference between the community index and a private index +stored locally on disk or on your own infrastructure. An index must be located +in a first level subdirectory of an accessible git repository or local +filesystem location (or optionally at the top level in the case of a local +filesystem index). This subdirectory should contain only an `index.toml` +file and one or more `cr/crate_name` subdirectories within which the crate +manifests themselves are located. The `index.toml` file contains one line with +the form `version = "x.x.x"`, specifying the index format used. The range of +versions Alire is compatible with can be found by running `alr version`, and +breaking changes are listed in +[BREAKING.md](https://github.com/alire-project/alire/blob/master/BREAKING.md). + +To start using such an index, run + +`alr index --add= --name=`, + +where `` is a human-friendly label that `alr` will use to refer to it. + +To publish a crate to a private index, run + +`alr publish --for-private-index [ ]` + +as described in the sections above, then place the manifest file it generates at +the indicated path (relative to the location of `index.toml`). + +Additions to indexes stored locally on the disk will take effect immediately, +unless the crate being published contains `"provides"` definitions, in which +case an index update will be required (either with `alr index --update-all`, or +through a scheduled auto-update) to ensure it is properly used by the dependency +solver. An index update will always be required when publishing to a git +repository index. diff --git a/doc/user-changes.md b/doc/user-changes.md index 21d633332..1cd072e0b 100644 --- a/doc/user-changes.md +++ b/doc/user-changes.md @@ -4,6 +4,8 @@ This document is a development diary summarizing changes in `alr` that notably affect the user experience. It is intended as a one-stop point for users to stay on top of `alr` new features. +## Release `2.1` + ## Release `2.0` ### `ALIRE_SETTINGS_DIR` replaces `ALR_CONFIG` diff --git a/resources/alr-qr-code-gradient.png b/resources/alr-qr-code-gradient.png new file mode 100644 index 000000000..a3a33abda Binary files /dev/null and b/resources/alr-qr-code-gradient.png differ diff --git a/scripts/alr-completion.bash b/scripts/alr-completion.bash index d9a97cefb..9337e6b71 100755 --- a/scripts/alr-completion.bash +++ b/scripts/alr-completion.bash @@ -5,6 +5,14 @@ if ! builtin type -P alr &>/dev/null; then return fi +# Disable index auto-update to avoid interference with commands below +if alr settings --global | grep -q index.auto_update= ; then + update_period=$(alr settings --global | grep index.auto_update= | cut -f2 -d=) +else + update_period=unset +fi +alr settings --global --set index.auto_update 0 + # Commands/Topics: all line-first words not starting with capital letter, after # COMMANDS _alr_commands=$(alr | grep COMMANDS -A 99 | awk '{print $1}' | grep -v '[[:upper:]]' | xargs) @@ -80,3 +88,8 @@ function _alr_completion() { # Bind the function that performs context-aware completion complete -F _alr_completion alr + +# Re-enable index auto-update to avoid interference with commands below +if [ "$update_period" != "unset" ]; then + alr settings --global --set index.auto_update $update_period +fi diff --git a/scripts/ci-github.sh b/scripts/ci-github.sh index 06800270e..0b28b2dcb 100755 --- a/scripts/ci-github.sh +++ b/scripts/ci-github.sh @@ -39,7 +39,7 @@ fi # Disable distro detection if supported if [ "${ALIRE_DISABLE_DISTRO:-}" == "true" ]; then - alr config --global --set distribution.disable_detection true + alr settings --global --set distribution.disable_detection true fi # For the record @@ -51,8 +51,8 @@ echo GNAT VERSION: gnatls -v echo ............................ -echo ALR VERSION: -alr version +echo "ALR VERSION (at $(which alr)):" +alr -d version echo ............................ # Set up index if not default: @@ -61,6 +61,10 @@ if [ "${INDEX:-}" != "" ]; then alr index --name default --add "$INDEX" fi +echo "ALR SETTINGS (global):" +alr settings --global +echo ............................ + echo ALR SEARCH: # List releases for the record alr -q -d search --list --external diff --git a/src/alire/alire-builds.adb b/src/alire/alire-builds.adb index f15ea0041..373bcbff2 100644 --- a/src/alire/alire-builds.adb +++ b/src/alire/alire-builds.adb @@ -1,13 +1,13 @@ with AAA.Strings; -with Alire.Settings.Builtins; -with Alire.Settings.Edit; +with Alire.Cache; with Alire.Directories; with Alire.Flags; with Alire.Paths.Vault; with Alire.Roots; +with Alire.Settings.Builtins; -with GNATCOLL.VFS; +with Den.Filesystem; package body Alire.Builds is @@ -56,16 +56,14 @@ package body Alire.Builds is Simple_Logging.Activity ("Syncing " & Release.Milestone.TTY_Image) with Unreferenced; - Success : Boolean := False; - use GNATCOLL.VFS; begin - GNATCOLL.VFS.Copy - (Create (+Src), - +Dst, - Success); - - Assert (Success, - "Could not sync build dir from " & Src & " to " & Dst); + Den.Filesystem.Create_Directory (Dst); + Den.Filesystem.Copy (Src, Dst); + exception + when E : others => + Log_Exception (E); + Raise_Checked_Error + ("Could not sync build dir from " & Src & " to " & Dst); end; -- At this point we can generate the final crate configuration @@ -83,7 +81,7 @@ package body Alire.Builds is ---------- function Path return Absolute_Path - is (Settings.Edit.Cache_Path + is (Cache.Path / Paths.Build_Folder_Inside_Working_Folder); ---------- diff --git a/src/alire/alire-cache.adb b/src/alire/alire-cache.adb new file mode 100644 index 000000000..e6aade61c --- /dev/null +++ b/src/alire/alire-cache.adb @@ -0,0 +1,126 @@ +with Ada.Calendar; + +with Alire.Directories; +with Alire.Paths; +with Alire.Platforms.Folders; +with Alire.Settings.Builtins; +with Alire.Settings.Edit; + +with Den.Du; + +package body Alire.Cache is + + use Alire.Directories.Operators; + + package Adirs renames Ada.Directories; + package Du is new Den.Du; + + ---------- + -- Path -- + ---------- + + function Path return Absolute_Path + is (if Settings.Builtins.Cache_Dir.Get /= "" then + Settings.Builtins.Cache_Dir.Get + elsif not Settings.Edit.Is_At_Default_Dir then + Settings.Edit.Path / Paths.Cache_Folder_Inside_Working_Folder + else + Platforms.Folders.Cache); + + ----------- + -- Usage -- + ----------- + + function Usage return Usages is + + Busy_Top : Simple_Logging.Ongoing := + Simple_Logging.Activity ("Listing"); + + Busy : Simple_Logging.Ongoing := Simple_Logging.Activity (""); + + Last_Check : Ada.Calendar.Time := Ada.Calendar.Clock; + + -------------- + -- Progress -- + -------------- + + procedure Progress (Path : String) is + use Ada.Calendar; + begin + if Clock - Last_Check >= 0.1 + and then Directories.Is_File (Path / Alire.Paths.Crate_File_Name) + then + Busy_Top.Step; + Busy.Step (Adirs.Simple_Name (Path)); + Last_Check := Clock; + end if; + end Progress; + + Tree : constant Du.Tree := Du.List (Path, + Progress => Progress'Access); + + ---------------- + -- Usage_Wrap -- + ---------------- + + procedure Usage_Wrap (Parent : in out Usages; + Children : Du.Tree; + Depth : Depths; + Branch : String := "" + -- Says if toolchains, releases, or builds + ) + is + begin + for Child of Children loop + declare + Branch : constant String + := (if Usage_Wrap.Branch /= "" + then Usage_Wrap.Branch + else Adirs.Simple_Name (Child.Element.Path)); + Wrapped_Children : Usages; + begin + + -- Wrap the children if we still have room to go down + + if Depth < Release or else + (Depth < Build + and then Branch = Paths.Build_Folder_Inside_Working_Folder) + then + Usage_Wrap (Wrapped_Children, + Child.Element.Children, + Depth => Depths'Succ (Depth), + Branch => Branch); + end if; + + -- Create the wrapped node at the current depth + + Parent.Insert + (Item' + (Depth => Depth, + Name => +Adirs.Simple_Name (Child.Element.Path), + Path => +Child.Element.Path, + Size => Child.Tree_Size, + Children => Wrapped_Children)); + end; + end loop; + end Usage_Wrap; + + begin + -- The root node should be the cache dir itself, unless there is still + -- no cache at all. + if Tree.Is_Empty then + return Item_Sets.Empty_Set; + elsif Tree.Length not in 1 then + raise Program_Error + with "Cache tree root length /= 1:" & Tree.Length'Image; + end if; + + -- Iterate the obtained tree wrapping contents as our usage type + return Result : Usages do + Usage_Wrap (Result, + Tree.First_Element.Element.Children, + Depths'First); + end return; + end Usage; + +end Alire.Cache; diff --git a/src/alire/alire-cache.ads b/src/alire/alire-cache.ads new file mode 100644 index 000000000..de930e045 --- /dev/null +++ b/src/alire/alire-cache.ads @@ -0,0 +1,111 @@ +with Ada.Containers.Indefinite_Ordered_Multisets; +with Ada.Directories; + +package Alire.Cache is + + -- Cache inspection and management. The cache is where we store all data + -- that, if not found, is re-downloaded or regenerated. This currently + -- comprises toolchains, pristine releases (the vault), builds, and the + -- user index fork clone when publishing. + + function Path return Absolute_Path; + -- The location for data that will be recreated if missing; its value in + -- precedence order is: + -- 1) Setting builtin 'cache.dir' + -- 2) if Alire.Settings.Path is overridden, Settings.Path/cache + -- 3) Platforms.Folders.Cache + + subtype Sizes is Ada.Directories.File_Size; + -- A size, in bytes + + -- The following builds a tree of items in the cache, that can be queried + -- to present information up to a level of detail. + + type Depths is (Location, Release, Build); + -- Locations are the top-level folders: toolchains, releases, builds. + -- Releases are a unique release milestone plus short commit. + -- Builds are synced copies for a release, named as the release + build id. + + type Base_Item is abstract tagged null record; + + function "<" (L, R : Base_Item'Class) return Boolean; + + function Depth (This : Base_Item'Class) return Depths; + + function Name (This : Base_Item'Class) return String; + + function Path (This : Base_Item'Class) return Absolute_Path; + + function Size (This : Base_Item'Class) return Sizes; + + package Item_Sets is + new Ada.Containers.Indefinite_Ordered_Multisets (Base_Item'Class); + + subtype Usages is Item_Sets.Set; + + function Children (This : Base_Item'Class) return Usages; + + function Usage return Usages; + -- Compute cache usage. First level is locations, second level is releases, + -- third level is builds. Within level, childen are sorted by size. + + type Item is new Base_Item with record + Depth : Depths; + Name : UString; + Path : Unbounded_Absolute_Path; + Size : Sizes; -- Accumulated size below this item + Children : Usages; + end record; + + function Element (This : Base_Item'Class) return Item is (Item (This)) + with Inline; + +private + + use type Sizes; + + -------------- + -- Children -- + -------------- + + function Children (This : Base_Item'Class) return Usages + is (This.Element.Children); + + ----------- + -- Depth -- + ----------- + + function Depth (This : Base_Item'Class) return Depths + is (This.Element.Depth); + + ---------- + -- Name -- + ---------- + + function Name (This : Base_Item'Class) return String + is (UStrings.To_String (This.Element.Name)); + + ---------- + -- Path -- + ---------- + + function Path (This : Base_Item'Class) return Absolute_Path + is (Absolute_Path (UStrings.To_String (This.Element.Path))); + + ---------- + -- Size -- + ---------- + + function Size (This : Base_Item'Class) return Sizes is (This.Element.Size); + + --------- + -- "<" -- + --------- + + function "<" (L, R : Base_Item'Class) return Boolean + is (L.Size > R.Size + or else + (L.Size = R.Size + and then L.Name < R.Name)); + +end Alire.Cache; diff --git a/src/alire/alire-crate_configuration.adb b/src/alire/alire-crate_configuration.adb index c886c523b..ba8eb43dd 100644 --- a/src/alire/alire-crate_configuration.adb +++ b/src/alire/alire-crate_configuration.adb @@ -367,14 +367,11 @@ package body Alire.Crate_Configuration is is use Config_Maps; begin - for C in This.Var_Map.Iterate loop - if (Crate = "" or else Name (C).As_String = Crate) - and then Element (C).Set_By = Must_Be_Set - then - return False; - end if; - end loop; - return True; + return + (for all C in This.Var_Map.Iterate => + not + ((Crate = "" or else Name (C).As_String = Crate) + and then Element (C).Set_By = Must_Be_Set)); end Is_Config_Complete; --------------------- diff --git a/src/alire/alire-dependencies-graphs.adb b/src/alire/alire-dependencies-graphs.adb index 22b0dced7..fa5a90273 100644 --- a/src/alire/alire-dependencies-graphs.adb +++ b/src/alire/alire-dependencies-graphs.adb @@ -72,13 +72,7 @@ package body Alire.Dependencies.Graphs is return Boolean is begin - for Dep of This loop - if +Dep.Dependent = Crate then - return True; - end if; - end loop; - - return False; + return (for some Dep of This => +Dep.Dependent = Crate); end Has_Dependencies; ----------- diff --git a/src/alire/alire-directories.adb b/src/alire/alire-directories.adb index 4c8142ce6..8824cd6d8 100644 --- a/src/alire/alire-directories.adb +++ b/src/alire/alire-directories.adb @@ -13,6 +13,9 @@ with Alire.Platforms.Folders; with Alire.VFS; with Alire.Utils; +with Den.Filesystem; +with Den.Walk; + with GNAT.String_Hash; with GNATCOLL.VFS; @@ -146,7 +149,7 @@ package body Alire.Directories is Keep_Links : constant String := (case Platforms.Current.Operating_System is when Linux => "-d", - when FreeBSD | MacOS => "-R", + when FreeBSD | OpenBSD | MacOS => "-R", when others => raise Program_Error with "Unsupported operation"); begin @@ -392,51 +395,35 @@ package body Alire.Directories is Max_Depth : Natural := Natural'Last) return AAA.Strings.Vector is + use all type Den.Kinds; Found : AAA.Strings.Vector; - procedure Locate (Folder : String; - Current_Depth : Natural; - Max_Depth : Natural) + ----------- + -- Check -- + ----------- + + procedure Check (Item : Den.Walk.Item; + Enter : in out Boolean; + Stop : in out Boolean) is - use Ada.Directories; - Search : Search_Type; begin - Start_Search (Search, Folder, "", - Filter => (Ordinary_File => True, - Directory => True, - others => False)); - - while More_Entries (Search) loop - declare - Current : Directory_Entry_Type; - begin - Get_Next_Entry (Search, Current); - if Kind (Current) = Directory then - if Simple_Name (Current) /= "." - and then - Simple_Name (Current) /= ".." - and then - Current_Depth < Max_Depth - then - Locate (Folder / Simple_Name (Current), - Current_Depth + 1, - Max_Depth); - end if; - elsif Kind (Current) = Ordinary_File - and then Simple_Name (Current) = Simple_Name (Name) - then - Found.Append (Folder / Name); - end if; - end; - end loop; + Stop := False; - End_Search (Search); - end Locate; + if Max_Depth < Natural'Last and then Item.Depth > Max_Depth then + Enter := False; + end if; + + if Den.Kind (Item.Path) = File + and then Den.Name (Item.Path) = Den.Name (Name) + then + Found.Append (Item.Path); + end if; + end Check; - use Ada.Directories; begin - if Exists (Folder) and then Kind (Folder) = Directory then - Locate (Folder, 0, Max_Depth); + if Den.Exists (Folder) and then Den.Kind (Folder) = Den.Directory then + Den.Walk.Find (Folder, + Check'Access); end if; return Found; @@ -451,7 +438,9 @@ package body Alire.Directories is return Any_Path is begin - return AAA.Directories.Relative_Path (Parent, Child); + return Result : constant Any_Path := + Den.Filesystem.Relative (Den.Scrub (Parent), + Den.Scrub (Child)); end Find_Relative_Path; ---------------------- @@ -820,33 +809,32 @@ package body Alire.Directories is Remove_From_Source : Boolean) is - Base : constant Absolute_Path := Adirs.Full_Name (Src); - Target : constant Absolute_Path := Adirs.Full_Name (Dst); + Base : constant Absolute_Path := Den.Filesystem.Absolute (Src); + Target : constant Absolute_Path := Den.Filesystem.Absolute (Dst); ----------- -- Merge -- ----------- procedure Merge - (Item : Ada.Directories.Directory_Entry_Type; + (Item : Any_Path; Stop : in out Boolean) is - use all type Adirs.File_Kind; - + use all type Den.Kinds; + Src : constant Absolute_Path := Den.Filesystem.Absolute (Item); Rel_Path : constant Relative_Path := - Find_Relative_Path (Base, Adirs.Full_Name (Item)); + Find_Relative_Path (Base, Src); -- If this proves to be too slow, we should do our own recursion, -- building the relative path along the way, as this is recomputing -- it for every file needlessly. Dst : constant Absolute_Path := Target / Rel_Path; - Src : constant Absolute_Path := Adirs.Full_Name (Item); begin Stop := False; -- Check if we must skip (we delete source file) - if Adirs.Kind (Item) = Ordinary_File + if Den.Kind (Item) /= Directory and then Skip_Top_Level_Files and then Base = Parent (Src) then @@ -856,7 +844,7 @@ package body Alire.Directories is -- Create a new dir if necessary - if Adirs.Kind (Item) = Directory then + if Den.Kind (Item) = Directory then if not Is_Directory (Dst) then Trace.Debug (" Merge: Creating destination dir " & Dst); Create_Tree (Dst); @@ -870,15 +858,15 @@ package body Alire.Directories is -- Copy file into place Trace.Debug (" Merge: copying " - & Adirs.Full_Name (Item) + & Den.Filesystem.Absolute (Item) & " into " & Dst); - if Adirs.Exists (Dst) then + if Den.Exists (Dst) then if Fail_On_Existing_File then Recoverable_User_Error ("Cannot copy " & TTY.URL (Src) & " into place, file already exists: " & TTY.URL (Dst)); - elsif Adirs.Kind (Dst) /= Ordinary_File then + elsif Den.Kind (Dst) /= File then Raise_Checked_Error ("Cannot overwrite " & TTY.URL (Dst) & " as it is not a regular file"); else @@ -912,7 +900,11 @@ package body Alire.Directories is exception when E : others => Trace.Error - ("When copying " & Src & " --> " & Dst & ": "); + ("When copying " & Src & " (" & Den.Kind (Src)'Image + & ") --> " & Dst & ": "); + Trace.Error + ("Src item was: " + & Item & " (" & Den.Kind (Item)'Image & ")"); Log_Exception (E, Error); raise; end; @@ -941,112 +933,60 @@ package body Alire.Directories is procedure Traverse_Tree (Start : Any_Path; Doing : access procedure - (Item : Ada.Directories.Directory_Entry_Type; + (Item : Any_Path; Stop : in out Boolean); Recurse : Boolean := False; Spinner : Boolean := False) is use Ada.Directories; - Visited : AAA.Strings.Set; - -- To avoid infinite recursion in case of softlinks pointed to parent - -- folders - Progress : Simple_Logging.Ongoing := Simple_Logging.Activity (Text => "Exploring " & Start, Level => (if Spinner then Info else Debug)); - procedure Go_Down (Item : Directory_Entry_Type); - - ---------------------------- - -- Traverse_Tree_Internal -- - ---------------------------- - - procedure Traverse_Tree_Internal - (Start : Any_Path; - Doing : access procedure - (Item : Ada.Directories.Directory_Entry_Type; - Stop : in out Boolean); - Recurse : Boolean := False) - is - pragma Unreferenced (Doing, Recurse); - begin - Search (Start, - "", - (Directory => True, Ordinary_File => True, others => False), - Go_Down'Access); - end Traverse_Tree_Internal; - ------------- -- Go_Down -- ------------- - procedure Go_Down (Item : Directory_Entry_Type) is - Stop : Boolean := False; - Prune : Boolean := False; - VF : constant VFS.Virtual_File := - VFS.New_Virtual_File (VFS.From_FS (Full_Name (Item))); - -- We use this later to check whether this is a soft link + procedure Go_Down (This : Den.Walk.Item; + Enter : in out Boolean; + Stop : in out Boolean) + is + use all type Den.Kinds; + Path : constant Any_Path := This.Path; begin + Enter := True; + Stop := False; - -- Ada.Directories reports softlinks not as special files but as the - -- target of the link. This confuses users of Traverse_Tree that may - -- see files within a folder that has never been visited before. - - -- Short of introducing new file kinds for softlinks and reporting - -- them to clients, for now we just ignore softlinks to dirs, and - -- this way only actual folders are traversed. - - if VF.Is_Symbolic_Link and then Kind (Item) = Directory then - Trace.Warning ("Skipping softlink dir during tree traversal: " - & Full_Name (Item)); + begin + Doing (This.Path, Stop); + exception + when Traverse_Tree_Prune_Dir => + Enter := False; + end; + if Stop then return; end if; - if Simple_Name (Item) /= "." and then Simple_Name (Item) /= ".." then - begin - Doing (Item, Stop); - exception - when Traverse_Tree_Prune_Dir => - Prune := True; - end; - if Stop then - return; - end if; - - if not Prune and then Recurse and then Kind (Item) = Directory then - declare - Normal_Name : constant Absolute_Path - := - String (GNATCOLL.VFS.Full_Name - (VFS.New_Virtual_File (Full_Name (Item)), - Normalize => True, - Resolve_Links => True).all); - begin - if Visited.Contains (Normal_Name) then - Trace.Debug ("Not revisiting " & Normal_Name); - else - Visited.Insert (Normal_Name); - if Spinner then - Progress.Step ("Exploring .../" & Simple_Name (Item)); - end if; - Traverse_Tree_Internal (Normal_Name, Doing, Recurse); - end if; - end; - elsif Prune and then Kind (Item) = Directory then - Trace.Debug ("Skipping dir: " & Full_Name (Item)); - elsif Prune and then Kind (Item) /= Directory then - Trace.Warning ("Pruning of non-dir entry has no effect: " - & Full_Name (Item)); + if Enter and then Recurse and then Den.Kind (Path) = Directory then + if Spinner then + Progress.Step ("Exploring .../" & Simple_Name (Path)); end if; + elsif not Enter and then Den.Kind (Path) = Directory then + Trace.Debug ("Skipping dir: " & Full_Name (Path)); + elsif not Enter and then Den.Kind (Path) /= Directory then + Trace.Warning ("Pruning of non-dir entry has no effect: " + & Full_Name (Path)); end if; end Go_Down; begin Trace.Debug ("Traversing folder: " & Adirs.Full_Name (Start)); - Traverse_Tree_Internal (Start, Doing, Recurse); + Den.Walk.Find (Start, + Action => Go_Down'Access, + Options => (Enter_Regular_Dirs => Recurse, others => <>)); end Traverse_Tree; --------------- @@ -1062,7 +1002,7 @@ package body Alire.Directories is -- Accumulate -- ---------------- - procedure Accumulate (Item : Directory_Entry_Type; + procedure Accumulate (Item : Any_Path; Stop : in out Boolean) is begin diff --git a/src/alire/alire-directories.ads b/src/alire/alire-directories.ads index bbf513751..250b4b709 100644 --- a/src/alire/alire-directories.ads +++ b/src/alire/alire-directories.ads @@ -114,7 +114,7 @@ package Alire.Directories is procedure Traverse_Tree (Start : Any_Path; Doing : access procedure - (Item : Ada.Directories.Directory_Entry_Type; + (Item : Any_Path; Stop : in out Boolean); Recurse : Boolean := False; Spinner : Boolean := False); diff --git a/src/alire/alire-expressions-maps.adb b/src/alire/alire-expressions-maps.adb index 3069c2e25..0b7b44737 100644 --- a/src/alire/alire-expressions-maps.adb +++ b/src/alire/alire-expressions-maps.adb @@ -7,12 +7,13 @@ package body Alire.Expressions.Maps is procedure Insert (M : in out Map; V : String; E : Elements) is begin - if V = TOML_Keys.Case_Others or else V = "others" then + if V in TOML_Keys.Case_Others | "others" then M.Set_Others (E); else if not M.Base.Is_Valid (V) and then Force then - Trace.Debug ("Storing unknown value '" & V & "' for enumeration '" - & Key (M.Base) & "'"); + Trace.Debug + ("Storing unknown value '" & V & "' for enumeration '" & + Key (M.Base) & "'"); end if; M.Entries.Insert (V, E); end if; diff --git a/src/alire/alire-expressions.ads b/src/alire/alire-expressions.ads index ee1d5e927..7abcf9889 100644 --- a/src/alire/alire-expressions.ads +++ b/src/alire/alire-expressions.ads @@ -30,11 +30,10 @@ package Alire.Expressions with Preelaborate is function Name (This : Variable) return String; -- The Ada-like name of this variable - function Satisfies (Property : Properties.Property'Class; - Var_Key : String; - Value : String) return Boolean - with Pre => Value /= TOML_Keys.Case_Others and then - Value /= "others"; + function Satisfies + (Property : Properties.Property'Class; Var_Key : String; Value : String) + return Boolean with + Pre => Value not in TOML_Keys.Case_Others | "others"; -- Say if a property is satisfied by the value, which must match the -- Variable and property type (keys must match). Doesn't accept defaults. diff --git a/src/alire/alire-index-search.adb b/src/alire/alire-index-search.adb index 7e7fb85ff..54adf8914 100644 --- a/src/alire/alire-index-search.adb +++ b/src/alire/alire-index-search.adb @@ -24,6 +24,11 @@ package body Alire.Index.Search is Busy : Simple_Logging.Ongoing := Simple_Logging.Activity ("Searching"); begin + -- Preserve old behavior for human output + if Utils.Tables.Structured_Output then + Table.Header ("NAME").Header ("DESCRIPTION"); + end if; + for Crate of Alire.Index.All_Crates.all loop if Lookup = "" or else Contains (To_Lower_Case (+Crate.Name), Lookup) or else @@ -37,7 +42,7 @@ package body Alire.Index.Search is Busy.Step; end loop; - if Found = 0 then + if Found = 0 and then not Utils.Tables.Structured_Output then Trace.Always ("No hits"); else Table.Print (Always, Separator => " "); diff --git a/src/alire/alire-install.adb b/src/alire/alire-install.adb index 319237029..133fcda61 100644 --- a/src/alire/alire-install.adb +++ b/src/alire/alire-install.adb @@ -395,7 +395,7 @@ package body Alire.Install is Result : Installed_Milestones; procedure Find - (Item : Ada.Directories.Directory_Entry_Type; + (Item : Any_Path; Stop : in out Boolean) is Name : constant String := Adirs.Simple_Name (Item); diff --git a/src/alire/alire-origins-deployers-system-apt.adb b/src/alire/alire-origins-deployers-system-apt.adb index 2207482f5..f81c63d39 100644 --- a/src/alire/alire-origins-deployers-system-apt.adb +++ b/src/alire/alire-origins-deployers-system-apt.adb @@ -25,13 +25,8 @@ package body Alire.Origins.Deployers.System.Apt is Valid_Exit_Codes => (0, 1), -- returned when not found Err_To_Out => True); begin - for Line of Output loop - if Line = "Status: install ok installed" then - return True; - end if; - end loop; - - return False; + return + (for some Line of Output => Line = "Status: install ok installed"); end Already_Installed; ------------ diff --git a/src/alire/alire-origins-deployers-system-zypper.adb b/src/alire/alire-origins-deployers-system-zypper.adb index fca28d6b2..716ee622b 100644 --- a/src/alire/alire-origins-deployers-system-zypper.adb +++ b/src/alire/alire-origins-deployers-system-zypper.adb @@ -30,13 +30,9 @@ package body Alire.Origins.Deployers.System.Zypper is Valid_Exit_Codes => (0, 104), -- returned when not found Err_To_Out => True); begin - for Line of Output loop - if Has_Prefix (Line, " + Has_Prefix (Line, " - if Commit'Length /= Git_Commit'Length then + if not Is_Valid_Commit (Commit) then Raise_Checked_Error ("invalid git commit id, " & "40 digits hexadecimal expected"); end if; return New_Git (VCS_URL, Commit, Subdir); when Hg => - if Commit'Length /= Hg_Commit'Length then + if not Is_Valid_Mercurial_Commit (Commit) then Raise_Checked_Error ("invalid mercurial commit id, " & "40 digits hexadecimal expected"); @@ -769,7 +769,10 @@ package body Alire.Origins is when VCS_Kinds => Table.Set (Keys.URL, - +(Prefixes (This.Kind).all + +((if This.Kind in Git + and then AAA.Strings.Has_Prefix (This.URL, "git@") + then "" + else Prefixes (This.Kind).all) & (if URI.Scheme (This.URL) in URI.None -- not needed for remote repos, but for testing -- ones used locally: diff --git a/src/alire/alire-origins.ads b/src/alire/alire-origins.ads index b24327cb8..cf67cd64d 100644 --- a/src/alire/alire-origins.ads +++ b/src/alire/alire-origins.ads @@ -130,6 +130,10 @@ package Alire.Origins is is (S'Length = Git_Commit'Length and then (for all Char of S => Char in Alire.Utils.Hexadecimal_Character)); + function Is_Valid_Mercurial_Commit (S : String) return Boolean + is (S'Length = Hg_Commit'Length and then + (for all Char of S => Char in Alire.Utils.Hexadecimal_Character)); + function Short_Commit (Commit : String) return String; -- First characters in the commit diff --git a/src/alire/alire-os_lib.ads b/src/alire/alire-os_lib.ads index b2c88852a..5b0b2b81a 100644 --- a/src/alire/alire-os_lib.ads +++ b/src/alire/alire-os_lib.ads @@ -65,9 +65,7 @@ private function To_Portable (Path : Any_Path) return Portable_Path_Like - is (case GNATCOLL.OS.Constants.OS is - when MacOS | Unix => Path, - when Windows => Replace (Path, "\", "/")); + is (Replace (Path, "\", "/")); -------------------- -- To_Native_Like -- @@ -75,7 +73,7 @@ private function To_Native (Path : Portable_Path_Like) return Native_Path_Like is (case GNATCOLL.OS.Constants.OS is - when MacOS | Unix => Path, + when MacOS | Unix => Replace (String (Path), "\", "/"), when Windows => Replace (String (Path), "/", "\")); end Alire.OS_Lib; diff --git a/src/alire/alire-paths-vault.ads b/src/alire/alire-paths-vault.ads index 0c6b1eeb4..274ba14b5 100644 --- a/src/alire/alire-paths-vault.ads +++ b/src/alire/alire-paths-vault.ads @@ -1,4 +1,4 @@ -with Alire.Settings.Edit; +with Alire.Cache; package Alire.Paths.Vault is @@ -10,7 +10,7 @@ package Alire.Paths.Vault is -- are run there (see Alire.Builds). function Path return Absolute_Path - is (Settings.Edit.Cache_Path + is (Cache.Path / Release_Folder_Inside_Working_Folder); end Alire.Paths.Vault; diff --git a/src/alire/alire-paths.ads b/src/alire/alire-paths.ads index 5b4a06824..6019836bb 100644 --- a/src/alire/alire-paths.ads +++ b/src/alire/alire-paths.ads @@ -27,9 +27,6 @@ package Alire.Paths with Preelaborate is Scripts_Graph_Easy : constant String := "graph-easy"; -- Script for ASCII graphs - Complete_Flag : constant String := "complete_copy"; - -- We use /alire/ to mark that a file operation was completed - private Crate_File_Extension_With_Dot : constant String := ".toml"; diff --git a/src/alire/alire-platforms-common.ads b/src/alire/alire-platforms-common.ads index 488f87041..707176934 100644 --- a/src/alire/alire-platforms-common.ads +++ b/src/alire/alire-platforms-common.ads @@ -20,7 +20,14 @@ private package Alire.Platforms.Common is --------------------- function Unix_Home_Folder return String - is (OS_Lib.Getenv ("HOME", Default => "/tmp")); + is (if OS_Lib.Getenv ("HOME", "unset") = "unset" and then + GNAT.OS_Lib.Directory_Separator = '\' + then + raise Checked_Error with + "$HOME is not set, you might be running an" + & " `alr` built for a non-Windows OS" + else + OS_Lib.Getenv ("HOME", Default => "/tmp")); ---------------------- -- Unix_Temp_Folder -- diff --git a/src/alire/alire-platforms.ads b/src/alire/alire-platforms.ads index f78e08e41..bf879bd7d 100644 --- a/src/alire/alire-platforms.ads +++ b/src/alire/alire-platforms.ads @@ -3,7 +3,7 @@ package Alire.Platforms with Preelaborate is -- Platform information necessary for some releases type Extended_Architectures is - (AMD64, -- Equivalent to X86_64 (FreeBSD) + (AMD64, -- Equivalent to X86_64 (FreeBSD/OpenBSD) ARM64, -- Equivalent to AARCH64 End_Of_Duplicates, -- Up to this point, these are architectures that we want to rename to @@ -21,6 +21,7 @@ package Alire.Platforms with Preelaborate is -- See e.g. https://stackoverflow.com/a/45125525/761390 type Operating_Systems is (FreeBSD, + OpenBSD, Linux, MacOS, Windows, diff --git a/src/alire/alire-properties-actions-runners.ads b/src/alire/alire-properties-actions-runners.ads index 5dd745e42..cfada5ed3 100644 --- a/src/alire/alire-properties-actions-runners.ads +++ b/src/alire/alire-properties-actions-runners.ads @@ -37,7 +37,7 @@ private Working_Folder : Any_Path (1 .. Folder_Len); end record with Type_Invariant => - (Name = "" or else Name in Action_Name) + (Name in "" | Action_Name) and then (if Moment = On_Demand then Name /= ""); diff --git a/src/alire/alire-properties-configurations.adb b/src/alire/alire-properties-configurations.adb index e3afa89f6..95234cbe5 100644 --- a/src/alire/alire-properties-configurations.adb +++ b/src/alire/alire-properties-configurations.adb @@ -380,12 +380,9 @@ package body Alire.Properties.Configurations is declare Str : constant String := Val.As_String; begin - for Index in 1 .. This.Values.Length loop - if This.Values.Item (Index).As_String = Str then - return True; - end if; - end loop; - return False; + return + (for some Index in 1 .. This.Values.Length => + This.Values.Item (Index).As_String = Str); end; when Real => diff --git a/src/alire/alire-properties-from_toml.ads b/src/alire/alire-properties-from_toml.ads index 00bed23b6..517335b6c 100644 --- a/src/alire/alire-properties-from_toml.ads +++ b/src/alire/alire-properties-from_toml.ads @@ -53,14 +53,12 @@ package Alire.Properties.From_TOML is Crates.External_Shared_Section => (Description | Maintainers | - Maintainers_Logins | Name => True, others => False), Crates.Index_Release => (Description | Maintainers | - Maintainers_Logins | Name | Version => True, others => False), diff --git a/src/alire/alire-properties-labeled.adb b/src/alire/alire-properties-labeled.adb index 4de28a105..d61d619c2 100644 --- a/src/alire/alire-properties-labeled.adb +++ b/src/alire/alire-properties-labeled.adb @@ -131,10 +131,12 @@ package body Alire.Properties.Labeled is end if; when Maintainers_Logins => - if not Utils.Is_Valid_GitHub_Username (L.Value) then + -- The crate may be published through a private index, so we don't + -- know the requirements for a valid username; reject only an + -- empty string. + if L.Value'Length = 0 then From.Checked_Error - ("maintainers-logins must be a valid GitHub login, but got: " - & L.Value); + ("maintainers-logins values must be non-empty"); end if; when Tag => diff --git a/src/alire/alire-publish-submit.adb b/src/alire/alire-publish-submit.adb index 122a5ec0e..dcfc96885 100644 --- a/src/alire/alire-publish-submit.adb +++ b/src/alire/alire-publish-submit.adb @@ -298,7 +298,7 @@ package body Alire.Publish.Submit is Target : constant Absolute_Path := Local_Repo_Path / VFS.To_Native - (TOML_Index.Manifest_Path (Context.Root.Value.Name)) + (TOML_Index.Community_Manifest_Path (Context.Root.Value.Name)) / Filename; begin Directories.Create_Tree (Directories.Parent (Target)); diff --git a/src/alire/alire-publish.adb b/src/alire/alire-publish.adb index 907b751fa..b62352360 100644 --- a/src/alire/alire-publish.adb +++ b/src/alire/alire-publish.adb @@ -173,13 +173,15 @@ package body Alire.Publish is -- New_Options -- ----------------- - function New_Options (Skip_Build : Boolean := False; - Skip_Submit : Boolean := False; - Manifest : String := Roots.Crate_File_Name) + function New_Options (Skip_Build : Boolean := False; + Skip_Submit : Boolean := False; + For_Private_Index : Boolean := False; + Manifest : String := Roots.Crate_File_Name) return All_Options - is (Manifest_File => +Manifest, - Skip_Build => Skip_Build, - Skip_Submit => Skip_Submit); + is (Manifest_File => +Manifest, + Skip_Build => Skip_Build, + Skip_Submit => Skip_Submit, + For_Private_Index => For_Private_Index); --------------- -- Git_Error -- @@ -228,8 +230,9 @@ package body Alire.Publish is ------------------- -- Check_Release -- ------------------- - -- Checks the presence of recommended/mandatory fileds in the release - procedure Check_Release (Release : Releases.Release) is + -- Checks the presence of recommended/mandatory fields in the release + procedure Check_Release (Release : Releases.Release; Context : in out Data) + is use CLIC.User_Input; Recommend : AAA.Strings.Vector; -- Optional @@ -294,6 +297,20 @@ package body Alire.Publish is end if; end loop; + -- The maintainers-logins field is mandatory only if publishing to the + -- community index + + if not Context.Options.For_Private_Index then + declare + Key_String : constant String := Tomify + (Properties.From_TOML.Maintainers_Logins'Image); + begin + if not Release.Has_Property (Key_String) then + Missing.Append (Key_String); + end if; + end; + end if; + Caret_Pre_1 := Release.Check_Caret_Warning; if not Missing.Is_Empty then @@ -312,6 +329,26 @@ package body Alire.Publish is & " not be pre-release versions."); end if; + -- If we are submitting to the community index, the maintainers-logins + -- values must be valid GitHub usernames + if not Context.Options.For_Private_Index then + for Property of Release.Maint_Logins loop + declare + Maint_Login : constant String := Property.To_TOML.As_String; + begin + if not Utils.Is_Valid_GitHub_Username (Maint_Login) then + Raise_Checked_Error ("The maintainer login '" + & Maint_Login + & "' is not a valid GitHub username"); + end if; + + -- We could also check GitHub.User_Exists at this point, but it + -- isn't worth the GitHub API call (running the testsuite a + -- couple of times would trigger GitHub's rate limits) + end; + end loop; + end if; + -- Final confirmation. We default to Yes if no recommended missing or -- Force. @@ -392,7 +429,8 @@ package body Alire.Publish is (Starting_Manifest (Context), Alire.Manifest.Local, Strict => True, - Root_Path => Adirs.Full_Name (+Context.Path))); + Root_Path => Adirs.Full_Name (+Context.Path)), + Context); -- Will have raised if the release is not loadable or incomplete else declare @@ -408,7 +446,7 @@ package body Alire.Publish is ("Invalid metadata found at " & Root.Value.Path, Root.Brokenness)); when Valid => - Check_Release (Root.Value.Release); + Check_Release (Root.Value.Release, Context); end case; end; end if; @@ -557,8 +595,8 @@ package body Alire.Publish is ("Your index manifest file has been generated at " & TTY.URL (Index_Manifest)); - -- Ask to submit, or show the upload URL if submission skipped, or a - -- more generic message otherwise (when lacking a github login). + -- Ask to submit, or provide submission instructions if submission + -- skipped. if not Context.Options.Skip_Submit then -- Safeguard to avoid tests creating a live pull request, unless @@ -580,23 +618,40 @@ package body Alire.Publish is then raise Early_Stop; end if; + elsif Context.Options.For_Private_Index then + -- We are publishing to a private index, the location of which is + -- unknown, so we can only give generic instructions on where to + -- place the file. + Put_Info + ("Please upload this file to the index in the " + & TTY.URL (String (TOML_Index.Manifest_Path (Name)) & "/") + & " subdirectory."); elsif not Settings.Builtins.User_Github_Login.Is_Empty then + -- The user has provided a GitHub login, so provide an upload URL + -- to create a pull request. Put_Info - ("Please upload this file to " + ("If you haven't already, please fork " + & TTY.URL (Tail (Index.Community_Repo, '+')) + & " to your GitHub."); + Put_Info + ("This file can then be uploaded to " & TTY.URL (Index.Community_Host & "/" & Settings.Builtins.User_Github_Login.Get & "/" & Index.Community_Repo_Name & "/upload/" & Index.Community_Branch & "/" - & String (TOML_Index.Manifest_Path (Name))) + & String (TOML_Index.Community_Manifest_Path (Name))) & " to create a pull request against the community index."); else + -- We don't have the user's GitHub username, so show a more + -- generic message. Put_Info ("Please create a pull request against the community index at " & TTY.URL (Tail (Index.Community_Repo, '+')) & " including this file at " - & TTY.URL (String (TOML_Index.Manifest_Path (Name)))); + & TTY.URL + (String (TOML_Index.Community_Manifest_Path (Name)) & "/")); end if; exception @@ -797,7 +852,7 @@ package body Alire.Publish is Root_Path => Adirs.Full_Name (+Context.Path)) .Replacing (Origin => Context.Origin); begin - Check_Release (Release); + Check_Release (Release, Context); end Show_And_Confirm; ------------------- @@ -867,18 +922,20 @@ package body Alire.Publish is if URI.Scheme (URL) not in URI.HTTP then -- A git@ URL is private to the user and should not be used for - -- packaging: + -- packaging via the the community index if AAA.Strings.Has_Prefix (URL, "git@") then - Raise_Checked_Error - ("The origin cannot use a private git remote: " & URL); - end if; + if not Context.Options.For_Private_Index then + Raise_Checked_Error + ("The origin cannot use a private remote: " & URL); + end if; -- Otherwise we assume this is a local path - - Recoverable_User_Error - ("The origin must be a definitive remote location, but is " & URL); - -- For testing we may want to allow local URLs, or may be for - -- internal use with network drives? So allow forcing it. + else + Recoverable_User_Error + ("The origin must be a definitive remote location, but is " & URL); + -- For testing we may want to allow local URLs, or may be for + -- internal use with network drives? So allow forcing it. + end if; end if; Put_Success ("Origin is of supported kind: " & Context.Origin.Kind'Img); @@ -897,10 +954,10 @@ package body Alire.Publish is Is_Trusted (URL) then Put_Success ("Origin is hosted on trusted site: " - & URI.Authority_Without_Credentials (URL)); + & URI.Host (URL)); else Raise_Checked_Error ("Origin is hosted on unknown site: " - & URI.Authority_Without_Credentials (URL)); + & URI.Host (URL)); end if; end if; @@ -1017,9 +1074,13 @@ package body Alire.Publish is Run_Steps (Context, (Step_Check_User_Manifest, Step_Prepare_Archive, - Step_Verify_Origin, - Step_Verify_Github, - Step_Deploy_Sources, + Step_Verify_Origin) + & + (if Options.Skip_Submit + then No_Steps + else (1 => Step_Verify_Github)) + & + (Step_Deploy_Sources, Step_Check_Build, Step_Show_And_Confirm, Step_Generate_Index_Manifest) @@ -1034,10 +1095,8 @@ package body Alire.Publish is ---------------- function Is_Trusted (URL : Alire.URL) return Boolean - is (for some Site of Trusted_Sites => - URI.Authority_Without_Credentials (URL) = Site - or else - Has_Suffix (URI.Authority (URL), "." & Site)); + is (for some Site of Trusted_Sites => URI.Host (URL) = Site + or else Has_Suffix (URI.Host (URL), "." & Site)); ---------------------- -- Local_Repository -- @@ -1164,9 +1223,16 @@ package body Alire.Publish is -- requires the owner keys. case URI.Scheme (Fetch_URL) is when URI.VCS_Schemes => - Raise_Checked_Error - ("The remote URL seems to require repository ownership: " - & Fetch_URL); + if Options.For_Private_Index then + Publish.Remote_Origin (URL => Raw_URL, + Commit => Commit, + Subdir => +Subdir, + Options => Options); + else + Raise_Checked_Error + ("The remote URL seems to require repository " + & "ownership: " & Fetch_URL); + end if; when URI.None | URI.Unknown => Publish.Remote_Origin (URL => "git+file:" & Raw_URL, Commit => Commit, @@ -1249,9 +1315,13 @@ package body Alire.Publish is Token => <>); begin Run_Steps (Context, - (Step_Verify_Origin, - Step_Verify_Github, - Step_Deploy_Sources, + (Step_Verify_Origin) + & + (if Options.Skip_Submit + then No_Steps + else (1 => Step_Verify_Github)) + & + (Step_Deploy_Sources, Step_Check_Build, Step_Show_And_Confirm, Step_Generate_Index_Manifest) diff --git a/src/alire/alire-publish.ads b/src/alire/alire-publish.ads index 1b256fe9b..9c892a4c9 100644 --- a/src/alire/alire-publish.ads +++ b/src/alire/alire-publish.ads @@ -8,9 +8,10 @@ package Alire.Publish is type All_Options is private; - function New_Options (Skip_Build : Boolean := False; - Skip_Submit : Boolean := False; - Manifest : String := Roots.Crate_File_Name) + function New_Options (Skip_Build : Boolean := False; + Skip_Submit : Boolean := False; + For_Private_Index : Boolean := False; + Manifest : String := Roots.Crate_File_Name) return All_Options; procedure Directory_Tar (Path : Any_Path := "."; @@ -55,9 +56,10 @@ package Alire.Publish is private type All_Options is tagged record - Manifest_File : UString; - Skip_Build : Boolean := False; - Skip_Submit : Boolean := False; + Manifest_File : UString; + Skip_Build : Boolean := False; + Skip_Submit : Boolean := False; + For_Private_Index : Boolean := False; end record; function Manifest (Options : All_Options) return Any_Path diff --git a/src/alire/alire-releases.ads b/src/alire/alire-releases.ads index f0ed1a32a..8e8ec152b 100644 --- a/src/alire/alire-releases.ads +++ b/src/alire/alire-releases.ads @@ -287,6 +287,8 @@ package Alire.Releases is function Maintainer (R : Release) return Alire.Properties.Vector; + function Maint_Logins (R : Release) return Alire.Properties.Vector; + function Milestone (R : Release) return Milestones.Milestone; function Website (R : Release) return Alire.Properties.Vector with @@ -521,6 +523,10 @@ private is (Conditional.Enumerate (R.Properties).Filter (Alire.TOML_Keys.Maintainer)); + function Maint_Logins (R : Release) return Alire.Properties.Vector + is (Conditional.Enumerate (R.Properties).Filter + (Alire.TOML_Keys.Maint_Logins)); + function Website (R : Release) return Alire.Properties.Vector is (Conditional.Enumerate (R.Properties).Filter (Alire.TOML_Keys.Website)); diff --git a/src/alire/alire-roots.adb b/src/alire/alire-roots.adb index 5b60531da..fae9448b8 100644 --- a/src/alire/alire-roots.adb +++ b/src/alire/alire-roots.adb @@ -19,6 +19,9 @@ with Alire.Toolchains.Solutions; with Alire.User_Pins.Maps; with Alire.Utils.TTY; with Alire.Utils.User_Input; +with Alire.VFS; + +with Den.Filesystem; with GNAT.OS_Lib; with GNAT.SHA256; @@ -1229,17 +1232,17 @@ package body Alire.Roots is Found : AAA.Strings.Set; -- Milestone --> Description procedure Check_Dir - (Item : Ada.Directories.Directory_Entry_Type; - Stop : in out Boolean) + (Item : Any_Path; + Stop : in out Boolean) is pragma Unreferenced (Stop); - use Ada.Directories; + use all type Den.Kinds; begin - if Kind (Item) /= Directory then + if Den.Kind (Item) /= Directory then return; end if; - if Simple_Name (Item) = Paths.Working_Folder_Inside_Root + if Den.Name (Item) = Paths.Working_Folder_Inside_Root then -- This is an alire metadata folder, don't go in. It could also be -- a crate named "alire" but that seems like a bad idea anyway. @@ -1250,14 +1253,23 @@ package body Alire.Roots is declare Opt : Optional.Root := - Optional.Detect_Root (Full_Name (Item)); + Optional.Detect_Root (Den.Filesystem.Full_Name (Item)); begin if Opt.Is_Valid then Found.Insert - (TTY.URL (Directories.Find_Relative_Path - (Starting_Path, Full_Name (Item))) & "/" - & Opt.Value.Release.Constant_Reference.Milestone.TTY_Image - & ": " & TTY.Emph + (TTY.URL + (String + (VFS.To_Portable + (Directories.Find_Relative_Path + (Den.Filesystem.Full_Name (Starting_Path), + Den.Filesystem.Full_Name (Item)))) + -- We use both full names because on Windows we see + -- mixed short/long names for the same path if we + -- apply Full_Name to only one of them. + & "/" + & Opt.Value.Release.Constant_Reference.Milestone.TTY_Image) + & ": " + & TTY.Emph (if Opt.Value.Release.Constant_Reference.Description /= "" then Opt.Value.Release.Constant_Reference.Description else "(no description)")); diff --git a/src/alire/alire-settings-edit.adb b/src/alire/alire-settings-edit.adb index 61e5a9c3e..dcc751174 100644 --- a/src/alire/alire-settings-edit.adb +++ b/src/alire/alire-settings-edit.adb @@ -242,18 +242,6 @@ package body Alire.Settings.Edit is end if; end Path; - ---------------- - -- Cache_Path -- - ---------------- - - function Cache_Path return Absolute_Path - is (if Builtins.Cache_Dir.Get /= "" then - Builtins.Cache_Dir.Get - elsif Path /= Default_Config_Path then - Path / Paths.Cache_Folder_Inside_Working_Folder - else - Platforms.Folders.Cache); - -------------- -- Set_Path -- -------------- diff --git a/src/alire/alire-settings-edit.ads b/src/alire/alire-settings-edit.ads index 196a66c9f..495e644eb 100644 --- a/src/alire/alire-settings-edit.ads +++ b/src/alire/alire-settings-edit.ads @@ -46,13 +46,6 @@ package Alire.Settings.Edit is -- * An ALIRE_SETTINGS_DIR env given folder -- * Default per-platform path (see alire-platforms-*) - function Cache_Path return Absolute_Path; - -- The location for data that will be recreated if missing; its value in - -- precedence order is: - -- 1) Setting builtin 'cache.dir' - -- 2) if Path above is overridden, Path/cache - -- 3) Platforms.Folders.Cache - procedure Set_Path (Path : Absolute_Path); -- Override global settings folder path diff --git a/src/alire/alire-solutions-diffs.adb b/src/alire/alire-solutions-diffs.adb index dc74563da..169ae6318 100644 --- a/src/alire/alire-solutions-diffs.adb +++ b/src/alire/alire-solutions-diffs.adb @@ -551,7 +551,7 @@ package body Alire.Solutions.Diffs is end loop; if Changed then - Table.Print (Level); + Table.Print (Level, Structured => False); Warn_Toolchain_Download; Warn_Unsatisfiable_GNAT_External; diff --git a/src/alire/alire-solutions.adb b/src/alire/alire-solutions.adb index 8e5228c40..d9609eb46 100644 --- a/src/alire/alire-solutions.adb +++ b/src/alire/alire-solutions.adb @@ -923,8 +923,17 @@ package body Alire.Solutions is Table : Utils.Tables.Table; begin if This.Links.Is_Empty and then Dependency_Map'(This.Pins).Is_Empty then - Trace.Always ("There are no pins"); + if Utils.Tables.Structured_Output then + Table.Print (Always); + else + Trace.Always ("There are no pins"); + end if; else + -- To preserve old behavior with human output + if Utils.Tables.Structured_Output then + Table.Header ("Crate").Header ("Target").Header ("Origin").New_Row; + end if; + for Dep of This.Dependencies loop if Dep.Is_Linked then Table diff --git a/src/alire/alire-toml_index.adb b/src/alire/alire-toml_index.adb index b60e714b0..e22a7096d 100644 --- a/src/alire/alire-toml_index.adb +++ b/src/alire/alire-toml_index.adb @@ -58,7 +58,7 @@ package body Alire.TOML_Index is -- describes a supported index, and that the file tree follows the proper -- naming conventions, without extraneous files being present. - procedure Load_Manifest (Item : Ada.Directories.Directory_Entry_Type; + procedure Load_Manifest (Item : Any_Path; Stop : in out Boolean); -- Check if entry is a candidate to manifest file, and in that case load -- its contents. May raise Checked_Error. @@ -273,7 +273,8 @@ package body Alire.TOML_Index is end return; when others => Result := - Outcome_Failure ("Several index.toml files found in index"); + Outcome_Failure ("Several index.toml files found in index: " + & Repo_Version_Files.Flatten (";")); return ""; end case; end Locate_Root; @@ -357,7 +358,7 @@ package body Alire.TOML_Index is -- Load_Manifest -- ------------------- - procedure Load_Manifest (Item : Ada.Directories.Directory_Entry_Type; + procedure Load_Manifest (Item : Any_Path; Stop : in out Boolean) is pragma Unreferenced (Stop); @@ -583,7 +584,14 @@ package body Alire.TOML_Index is Name : constant String := +Crate; begin return Portable_Path - ("index/" & Name (Name'First .. Name'First + 1) & "/" & Name); + (Name (Name'First .. Name'First + 1) & "/" & Name); end Manifest_Path; + ----------------------------- + -- Community_Manifest_Path -- + ----------------------------- + + function Community_Manifest_Path (Crate : Crate_Name) return Portable_Path + is ("index/" & Manifest_Path (Crate)); + end Alire.TOML_Index; diff --git a/src/alire/alire-toml_index.ads b/src/alire/alire-toml_index.ads index 7a481ca5a..4de76865d 100644 --- a/src/alire/alire-toml_index.ads +++ b/src/alire/alire-toml_index.ads @@ -18,6 +18,11 @@ package Alire.TOML_Index is -- Get the expected location of a crate manifest in an index. The result is -- portable; that is, always uses forward slashes. + function Community_Manifest_Path (Crate : Crate_Name) return Portable_Path; + -- Get the expected location of a crate manifest according to the community + -- index's convention (i.e. with everything under the "index/" directory). + -- The result is portable; that is, always uses forward slashes. + procedure Load (Index : Index_On_Disk.Index'Class; Strict : Boolean; diff --git a/src/alire/alire-toolchains.adb b/src/alire/alire-toolchains.adb index 08a118e43..2e70dba39 100644 --- a/src/alire/alire-toolchains.adb +++ b/src/alire/alire-toolchains.adb @@ -3,7 +3,7 @@ with AAA.Text_IO; with Ada.Containers.Indefinite_Vectors; with Ada.Directories; -with Alire.Settings.Edit; +with Alire.Cache; with Alire.Directories; with Alire.Index; with Alire.Manifest; @@ -12,6 +12,7 @@ with Alire.Paths; with Alire.Platforms.Current; with Alire.Properties; with Alire.Root; +with Alire.Settings.Edit; with Alire.Toolchains.Solutions; with Alire.Warnings; @@ -527,7 +528,7 @@ package body Alire.Toolchains is -- Detect -- ------------ - procedure Detect (Item : Ada.Directories.Directory_Entry_Type; + procedure Detect (Item : Any_Path; Stop : in out Boolean) is use Ada.Directories; @@ -610,7 +611,7 @@ package body Alire.Toolchains is function Path return Absolute_Path is (if Settings.Builtins.Toolchain_Dir.Get /= "" then Settings.Builtins.Toolchain_Dir.Get - else Settings.Edit.Cache_Path / "toolchains"); + else Cache.Path / "toolchains"); ------------ -- Deploy -- diff --git a/src/alire/alire-uri.adb b/src/alire/alire-uri.adb index 1c8e47a48..afd90830f 100644 --- a/src/alire/alire-uri.adb +++ b/src/alire/alire-uri.adb @@ -49,6 +49,36 @@ package body Alire.URI is Unknown); end Scheme; + ---------- + -- Host -- + ---------- + + function Host (This : URL) return String is + use AAA.Strings; + Auth : constant String := Authority_Without_Credentials (This); + begin + if Scheme (This) in File_Schemes then + return ""; + elsif Has_Prefix (This, "git@") + and then not Contains (Head (This, ":"), "/") + then + -- This has the form git@X:Y, so return X + return Head (Tail (This, "@"), ":"); + else + -- This is a normal URI, so return with any trailing port removed + -- (note that the host may be an IPv6 address in square brackets) + if Has_Prefix (Auth, "[") then + if Contains (Auth, "]:") then + return Head (Auth, "]:") & "]"; + else + return Auth; + end if; + else + return Head (Auth, ":"); + end if; + end if; + end Host; + package body Operators is --------- diff --git a/src/alire/alire-uri.ads b/src/alire/alire-uri.ads index d7db14cf7..4265896d7 100644 --- a/src/alire/alire-uri.ads +++ b/src/alire/alire-uri.ads @@ -77,6 +77,14 @@ package Alire.URI with Preelaborate is function Authority_Without_Credentials (This : URL) return String; -- Only the part after @ in an authority + function Host (This : URL) return String; + -- The host part of a remote URI + -- + -- Remotes of the form 'git@host.name:/some/path' (which are not valid + -- URIs) return the 'host.name' part. + -- + -- Returns an empty string for local URIs. + function Local_Path (This : URL) return String with Pre => Scheme (This) in None | File or else raise Checked_Error with Errors.Set diff --git a/src/alire/alire-user_pins.adb b/src/alire/alire-user_pins.adb index adf22e83d..cc9a4f725 100644 --- a/src/alire/alire-user_pins.adb +++ b/src/alire/alire-user_pins.adb @@ -7,6 +7,9 @@ with Alire.Utils.User_Input; with Alire.Utils.TTY; with Alire.VFS; +with Ada.Strings.Unbounded; +with AAA.Strings; + with GNAT.OS_Lib; package body Alire.User_Pins is @@ -465,6 +468,8 @@ package body Alire.User_Pins is ----------------- function Load_Remote return Pin is + use Ada.Strings.Unbounded; + use AAA.Strings; Result : Pin := (Kind => To_Git, URL => +This.Checked_Pop (Keys.URL, @@ -473,6 +478,21 @@ package body Alire.User_Pins is Commit => <>, Local_Path => <>); begin + -- "git+ssh://"" and "ssh+git://" are deprecated, so treat them as + -- identical to "ssh://" + if Has_Prefix (To_String (Result.URL), "git+ssh://") + or else Has_Prefix (To_String (Result.URL), "ssh+git://") + then + Replace_Slice (Result.URL, 1, 7, "ssh"); + end if; + + -- Likewise, anything of the form "xyz+https://" should be treated + -- as just "https://" + if Contains (To_String (Result.URL), "+http") then + Result.URL := To_Unbounded_String + (Tail (To_String (Result.URL), '+')); + end if; + if This.Contains (Keys.Branch) and then This.Contains (Keys.Commit) then diff --git a/src/alire/alire-utils-did_you_mean.adb b/src/alire/alire-utils-did_you_mean.adb index c7d096979..75c492bec 100644 --- a/src/alire/alire-utils-did_you_mean.adb +++ b/src/alire/alire-utils-did_you_mean.adb @@ -81,10 +81,10 @@ package body Alire.Utils.Did_You_Mean with Preelaborate is for V in Enum loop Possible_Values.Append (case Transform is - when None => V'Img, + when None => V'Img, when Lower_Case => AAA.Strings.To_Lower_Case (V'Img), - when Upper_Case => AAA.Strings.To_Lower_Case (V'Img), - when Tomify => TOML_Adapters.Tomify (V'Img)); + when Upper_Case => AAA.Strings.To_Upper_Case (V'Img), + when Tomify => TOML_Adapters.Tomify (V'Img)); end loop; return Suggestion (Input, Possible_Values); diff --git a/src/alire/alire-utils-tables.adb b/src/alire/alire-utils-tables.adb index a4ea9d76a..243a68442 100644 --- a/src/alire/alire-utils-tables.adb +++ b/src/alire/alire-utils-tables.adb @@ -6,27 +6,39 @@ package body Alire.Utils.Tables is -- Header -- ------------ + overriding procedure Header (T : in out Table; Cell : String) is + Text : constant String := + (if Structured_Output + then AAA.Strings.To_Lower_Case (Cell) + else TTY.Emph (AAA.Strings.To_Upper_Case (Cell))); begin - T.Append (TTY.Emph (AAA.Strings.To_Upper_Case (Cell))); + Parent (T).Header (Text); end Header; + ------------ + -- Header -- + ------------ + + overriding function Header (T : aliased in out Table; Cell : String) - return access Table is + return AAA.Table_IO.Reference + is begin T.Header (Cell); - return T'Access; + return AAA.Table_IO.Reference'(Table => T'Access); end Header; ----------- -- Print -- ----------- - procedure Print (T : Table; - Level : Trace.Levels := Info; - Separator : String := " "; - Align : AAA.Table_IO.Alignments := (1 .. 0 => <>)) + procedure Print (T : Table; + Level : Trace.Levels := Info; + Separator : String := " "; + Align : AAA.Table_IO.Alignments := (1 .. 0 => <>); + Structured : Boolean := Structured_Output) is procedure Print (Line : String) is @@ -37,9 +49,14 @@ package body Alire.Utils.Tables is end Print; begin - T.Print (Separator => Separator, - Align => Align, - Put_Line => Print'Access); + if Structured then + T.Print (Structured_Output_Format, + Put_Line => Print'Access); + else + T.Print (Separator => Separator, + Align => Align, + Put_Line => Print'Access); + end if; end Print; end Alire.Utils.Tables; diff --git a/src/alire/alire-utils-tables.ads b/src/alire/alire-utils-tables.ads index f6f3ef93f..19dd53d53 100644 --- a/src/alire/alire-utils-tables.ads +++ b/src/alire/alire-utils-tables.ads @@ -1,19 +1,33 @@ with AAA.Table_IO; -package Alire.Utils.Tables with Preelaborate is +with LML; - type Table is new AAA.Table_IO.Table with null record; +package Alire.Utils.Tables is + subtype Formats is LML.Formats; + + Structured_Output : Boolean := False; + + Structured_Output_Format : Formats; + + subtype Parent is AAA.Table_IO.Table; + + type Table is new Parent with null record; + + overriding procedure Header (T : in out Table; Cell : String); + overriding function Header (T : aliased in out Table; Cell : String) - return access Table; + return AAA.Table_IO.Reference; - procedure Print (T : Table; - Level : Trace.Levels := Info; - Separator : String := " "; - Align : AAA.Table_IO.Alignments := (1 .. 0 => <>)); - -- Hook so tables use the default output facilities of Alire + procedure Print (T : Table; + Level : Trace.Levels := Info; + Separator : String := " "; + Align : AAA.Table_IO.Alignments := (1 .. 0 => <>); + Structured : Boolean := Structured_Output); + -- Hook so tables use the default output facilities of Alire. When + -- Structured_Output is enabled, formatting information is ignored. end Alire.Utils.Tables; diff --git a/src/alire/alire-utils-user_input-query_config.adb b/src/alire/alire-utils-user_input-query_config.adb index 50e110c3c..2f0928710 100644 --- a/src/alire/alire-utils-user_input-query_config.adb +++ b/src/alire/alire-utils-user_input-query_config.adb @@ -41,15 +41,23 @@ package body Alire.Utils.User_Input.Query_Config is Default => "Your Name", Validation => null)); + --------------------------------------- + -- Is_Empty_Or_Valid_GitHub_Username -- + --------------------------------------- + + function Is_Empty_Or_Valid_GitHub_Username (Str : String) return Boolean + is (Str = "" or else Is_Valid_GitHub_Username (Str)); + ----------------------- -- User_GitHub_Login -- ----------------------- function User_GitHub_Login return String - is (Config_Or_Query_String (Config_Key => "user.github_login", - Question => "Please enter your GitHub login:", - Default => "github-username", - Validation => Is_Valid_GitHub_Username'Access)); + is (Config_Or_Query_String + (Config_Key => "user.github_login", + Question => "Please enter your GitHub login:", + Default => "", + Validation => Is_Empty_Or_Valid_GitHub_Username'Access)); ----------------- -- Check_Email -- diff --git a/src/alire/alire-utils-user_input-query_config.ads b/src/alire/alire-utils-user_input-query_config.ads index 79aede3ab..5ce459b5c 100644 --- a/src/alire/alire-utils-user_input-query_config.ads +++ b/src/alire/alire-utils-user_input-query_config.ads @@ -26,7 +26,9 @@ package Alire.Utils.User_Input.Query_Config is function User_Name return String; function User_GitHub_Login return String - with Post => (Is_Valid_GitHub_Username (User_GitHub_Login'Result)); + with Post => + (User_GitHub_Login'Result = "" + or else Is_Valid_GitHub_Username (User_GitHub_Login'Result)); function User_Email return String with Post => Could_Be_An_Email (User_Email'Result, With_Name => False); diff --git a/src/alire/alire-utils.adb b/src/alire/alire-utils.adb index 1b48243d8..fae78c296 100644 --- a/src/alire/alire-utils.adb +++ b/src/alire/alire-utils.adb @@ -16,13 +16,9 @@ package body Alire.Utils is function Command_Line_Contains (Prefix : String) return Boolean is begin - for I in 1 .. Ada.Command_Line.Argument_Count loop - if Has_Prefix (Ada.Command_Line.Argument (I), Prefix) then - return True; - end if; - end loop; - - return False; + return + (for some I in 1 .. Ada.Command_Line.Argument_Count => + Has_Prefix (Ada.Command_Line.Argument (I), Prefix)); end Command_Line_Contains; ------------- diff --git a/src/alire/alire-vcss-git.adb b/src/alire/alire-vcss-git.adb index 890374b04..f819ac365 100644 --- a/src/alire/alire-vcss-git.adb +++ b/src/alire/alire-vcss-git.adb @@ -484,7 +484,7 @@ package body Alire.VCSs.Git is -- Prepare Ref to make it less ambiguous - if Ref = "HEAD" or else Ref = "" then + if Ref in "HEAD" | "" then return This.Remote_Commit (From, ASCII.HT & "HEAD"); elsif Ref (Ref'First) not in '/' | ASCII.HT then return This.Remote_Commit (From, '/' & Ref); diff --git a/src/alire/alire-version.ads b/src/alire/alire-version.ads index 36185b621..a3aba7eec 100644 --- a/src/alire/alire-version.ads +++ b/src/alire/alire-version.ads @@ -16,8 +16,7 @@ private -- be replaced by `alr build` with the current commit, and appended with -- "_or_later" after build. - Current_Str : constant String := "2.0.2"; - -- 2.0.2: quarterly bugfix maintenance release + Current_Str : constant String := "2.1-dev"; -- 2.0.1: fix `alr install` and minor fixes -- 2.0.0: alr settings refactor and minor fixes -- 2.0.0-rc1: release candidate for 2.0 diff --git a/src/alire/os_openbsd/alire-platforms-current__openbsd.adb b/src/alire/os_openbsd/alire-platforms-current__openbsd.adb new file mode 100644 index 000000000..415056eea --- /dev/null +++ b/src/alire/os_openbsd/alire-platforms-current__openbsd.adb @@ -0,0 +1,40 @@ + +package body Alire.Platforms.Current is + + -- OpenBSD implementation (very close to Linux/FreeBSD) + + --------------------------- + -- Detected_Distribution -- + --------------------------- + + function Detected_Distribution return Platforms.Distributions + is (Platforms.Distribution_Unknown); + + ----------------------- + -- Distribution_Root -- + ----------------------- + + function Distribution_Root return Absolute_Path + is ("/"); + + ---------------------- + -- Load_Environment -- + ---------------------- + + procedure Load_Environment (Ctx : in out Alire.Environment.Context) + is null; + + ---------------------- + -- Operating_System -- + ---------------------- + + function Operating_System return Alire.Platforms.Operating_Systems + is (Alire.Platforms.OpenBSD); + + ---------------- + -- Initialize -- + ---------------- + + procedure Initialize is null; + +end Alire.Platforms.Current; diff --git a/src/alire/os_openbsd/alire-platforms-folders__openbsd.adb b/src/alire/os_openbsd/alire-platforms-folders__openbsd.adb new file mode 100644 index 000000000..51b4d1be7 --- /dev/null +++ b/src/alire/os_openbsd/alire-platforms-folders__openbsd.adb @@ -0,0 +1,34 @@ +with Ada.Directories; + +with Alire.Platforms.Common; + +package body Alire.Platforms.Folders is + + -- Linux implementation + + ----------- + -- Cache -- + ----------- + + function Cache return Absolute_Path is (Common.XDG_Data_Home); + + ----------- + -- Config-- + ----------- + + function Config return Absolute_Path is (Common.XDG_Config_Home); + + ---------- + -- Home -- + ---------- + + function Home return Absolute_Path is (Common.Unix_Home_Folder); + + ---------- + -- Temp -- + ---------- + + function Temp return Absolute_Path + is (Ada.Directories.Full_Name (Common.Unix_Temp_Folder)); + +end Alire.Platforms.Folders; diff --git a/src/alire/os_windows/alire-platforms-current__windows.adb b/src/alire/os_windows/alire-platforms-current__windows.adb index d4ccb3140..8ec2f3fd6 100644 --- a/src/alire/os_windows/alire-platforms-current__windows.adb +++ b/src/alire/os_windows/alire-platforms-current__windows.adb @@ -88,7 +88,20 @@ package body Alire.Platforms.Current is return Detect_Msys2_Root; when others => - return OS_Lib.Getenv ("HOMEDRIVE"); + declare + Root : constant String := OS_Lib.Getenv ("HOMEDRIVE", "C:\"); + begin + if Root'Length not in 2 | 3 then + Raise_Checked_Error + ("$HOMEDRIVE is not a proper drive: " & Root); + end if; + + if Root (Root'Last) not in '/' | '\' then + return Root & '\'; + else + return Root; + end if; + end; end case; end Distribution_Root; @@ -160,7 +173,7 @@ package body Alire.Platforms.Current is Trace.Detail ("Alire is configured not to install msys2."); Trace.Detail - ("Run 'alr config --global --set msys2.do_not_install false'" & + ("Run 'alr settings --global --set msys2.do_not_install false'" & " if you want Alire to install msys2."); return False; end if; diff --git a/src/alire/os_windows/alire-platforms-folders__windows.adb b/src/alire/os_windows/alire-platforms-folders__windows.adb index f2f2dc7bd..ea9f50fb9 100644 --- a/src/alire/os_windows/alire-platforms-folders__windows.adb +++ b/src/alire/os_windows/alire-platforms-folders__windows.adb @@ -2,6 +2,8 @@ with Ada.Directories; with Alire.OS_Lib; +with GNAT.OS_Lib; + package body Alire.Platforms.Folders is use OS_Lib.Operators; @@ -11,7 +13,18 @@ package body Alire.Platforms.Folders is ---------- function Home return Absolute_Path - is (OS_Lib.Getenv ("USERPROFILE")); + is + begin + if OS_Lib.Getenv ("USERPROFILE", "unset") = "unset" and then + GNAT.OS_Lib.Directory_Separator = '/' + then + Raise_Checked_Error + ("$USERPROFILE not set " + & "(might you be running an `alr` built for Windows?)"); + else + return OS_Lib.Getenv ("USERPROFILE"); + end if; + end Home; ----------- -- Cache -- diff --git a/src/alire/os_windows/alire-settings-builtins-windows.ads b/src/alire/os_windows/alire-settings-builtins-windows.ads index 588faa87f..34ed843f1 100644 --- a/src/alire/os_windows/alire-settings-builtins-windows.ads +++ b/src/alire/os_windows/alire-settings-builtins-windows.ads @@ -1,6 +1,7 @@ with AAA.Strings; -- Ensure config is loaded for some defaults below +with Alire.Cache; with Alire.Settings.Edit.Early_Load; pragma Unreferenced (Alire.Settings.Edit.Early_Load); @@ -32,7 +33,7 @@ package Alire.Settings.Builtins.Windows is Msys2_Install_Dir : constant Builtin := New_Builtin (Key => "msys2.install_dir", Kind => Stn_Absolute_Path, - Def => Settings.Edit.Cache_Path / "msys64", + Def => Cache.Path / "msys64", Help => "Directory where Alire will detect and/or install" & " msys2 system package manager. (Windows only)"); diff --git a/src/alr/alr-commands-cache.adb b/src/alr/alr-commands-cache.adb new file mode 100644 index 000000000..ee2c04511 --- /dev/null +++ b/src/alr/alr-commands-cache.adb @@ -0,0 +1,42 @@ +with Alire.Cache; +with Alire.Directories; +with Alire.Utils.Tables; + +package body Alr.Commands.Cache is + + ------------- + -- Summary -- + ------------- + + procedure Summary is + use Alire.Directories; + Table : Alire.Utils.Tables.Table; + Usage : constant Alire.Cache.Usages := Alire.Cache.Usage; + begin + Table + .Append ("Path:") + .Append (Alire.Cache.Path) + .New_Row; + + Table + .Append ("Size:") + .Append (TTY_Image (if Usage.Is_Empty + then 0 + else Alire.Cache.Usage.First_Element.Size)); + + Table.Print (Trace.Always); + end Summary; + + ------------- + -- Execute -- + ------------- + + overriding + procedure Execute (Cmd : in out Command; + Args : AAA.Strings.Vector) + is + begin + Summary; + end Execute; + +end Alr.Commands.Cache; diff --git a/src/alr/alr-commands-cache.ads b/src/alr/alr-commands-cache.ads new file mode 100644 index 000000000..2f933908d --- /dev/null +++ b/src/alr/alr-commands-cache.ads @@ -0,0 +1,43 @@ +with AAA.Strings; + +package Alr.Commands.Cache is + + type Command is new Commands.Command with private; + + overriding + function Name (Cmd : Command) return CLIC.Subcommand.Identifier + is ("cache"); + + overriding + procedure Execute (Cmd : in out Command; + Args : AAA.Strings.Vector); + -- This is called once the command-line is parsed. + + overriding + function Long_Description (Cmd : Command) + return AAA.Strings.Vector + is (AAA.Strings.Empty_Vector + .Append ("Inspect and manage Alire's cache.") + .New_Line + .Append ("Cache entries can be deleted to reclaim space and will be " + & "recreated on demand. Beware that deleting toolchains and releases " + & "may cause potentially large redownloads.")); + + overriding + procedure Setup_Switches + (Cmd : in out Command; + Config : in out CLIC.Subcommand.Switches_Configuration) is null; + + overriding + function Short_Description (Cmd : Command) return String + is ("Inspect and manage Alire's cache"); + + overriding + function Usage_Custom_Parameters (Cmd : Command) return String + is (""); + +private + + type Command is new Commands.Command with null record; + +end Alr.Commands.Cache; diff --git a/src/alr/alr-commands-clean.adb b/src/alr/alr-commands-clean.adb index d22918736..1e58bc496 100644 --- a/src/alr/alr-commands-clean.adb +++ b/src/alr/alr-commands-clean.adb @@ -42,7 +42,7 @@ package body Alr.Commands.Clean is -- Add_Target -- ---------------- - procedure Add_Target (Item : Ada.Directories.Directory_Entry_Type; + procedure Add_Target (Item : Alire.Any_Path; Unused_Stop : in out Boolean) is use Ada.Directories; diff --git a/src/alr/alr-commands-get.adb b/src/alr/alr-commands-get.adb index 5423414bf..f1f41faea 100644 --- a/src/alr/alr-commands-get.adb +++ b/src/alr/alr-commands-get.adb @@ -361,7 +361,9 @@ package body Alr.Commands.Get is else Reportaise_Command_Failed ("Crate [" & Allowed.Crate.As_String - & "] does not exist in the index."); + & "] does not exist in the index. " + & "Use `alr search " & Allowed.Crate.As_String + & "` for similar names."); end if; end if; diff --git a/src/alr/alr-commands-index.adb b/src/alr/alr-commands-index.adb index 9f261746e..cb212a177 100644 --- a/src/alr/alr-commands-index.adb +++ b/src/alr/alr-commands-index.adb @@ -1,10 +1,8 @@ -with AAA.Table_IO; - with Alire.Settings.Edit; with Alire.Index; with Alire.Index_On_Disk.Loading; with Alire.Index_On_Disk.Updates; -with Alire.Utils; +with Alire.Utils.Tables; package body Alr.Commands.Index is @@ -155,7 +153,7 @@ package body Alr.Commands.Index is Index_Load.Find_All (Alire.Settings.Edit.Indexes_Directory, Result); - Table : AAA.Table_IO.Table; + Table : Alire.Utils.Tables.Table; Count : Natural := 0; begin if not Result.Success then @@ -163,10 +161,10 @@ package body Alr.Commands.Index is end if; Table - .Append (TTY.Emph ("#")) - .Append (TTY.Emph ("NAME")) - .Append (TTY.Emph ("URL")) - .Append (TTY.Emph ("PATH")); + .Header ("#") + .Header ("NAME") + .Header ("URL") + .Header ("PATH"); if Alire.Log_Level = Alire.Trace.Debug then Table.Append (TTY.Emph ("PRIORITY")); @@ -187,7 +185,7 @@ package body Alr.Commands.Index is end loop; if Count > 0 then - Table.Print; + Table.Print (Always); else Trace.Info ("No index configured."); end if; diff --git a/src/alr/alr-commands-init.adb b/src/alr/alr-commands-init.adb index 0895c203f..102c2f349 100644 --- a/src/alr/alr-commands-init.adb +++ b/src/alr/alr-commands-init.adb @@ -269,7 +269,9 @@ package body Alr.Commands.Init is Put_Line ("authors = " & Arr (Q (Username))); Put_Line ("maintainers = " & Arr (Q (Username & " <" & Email & ">"))); - Put_Line ("maintainers-logins = " & Arr (Q (Login))); + if Login /= "" then + Put_Line ("maintainers-logins = " & Arr (Q (Login))); + end if; Put_Line ("licenses = " & Q (Info.Licenses)); Put_Line ("website = " & Q (Info.Website)); Put_Line ("tags = " & Q_Arr (Info.Tags)); @@ -473,6 +475,23 @@ package body Alr.Commands.Init is end if; end Query_License; + ------------------------ + -- Query_GitHub_Login -- + ------------------------ + + procedure Query_GitHub_Login (Info : in out Crate_Init_Info) is + begin + if Alire.Settings.Builtins.User_Github_Login.Is_Empty then + AAA.Text_IO.Put_Paragraph + ("If you intend to publish this crate to the community index, you " + & "will need a GitHub account with which to submit a pull " + & "request, which can optionally be configured now (leave blank " + & "to skip)."); + end if; + Info.GitHub_Login := To_Unbounded_String + (UI.Query_Config.User_GitHub_Login); + end Query_GitHub_Login; + ---------------------- -- Query_Crate_Kind -- ---------------------- @@ -582,27 +601,16 @@ package body Alr.Commands.Init is is use Alire.Settings; Info : Crate_Init_Info; + User_Not_Already_Configured : constant Boolean := + Builtins.User_Email.Is_Empty + or else Builtins.User_Name.Is_Empty + or else Builtins.User_Github_Login.Is_Empty; begin if Cmd.Bin and then Cmd.Lib then Reportaise_Wrong_Arguments ("Please provide either --bin or --lib"); end if; - if Builtins.User_Email.Is_Empty or else - Builtins.User_Name.Is_Empty or else - Builtins.User_Github_Login.Is_Empty - then - AAA.Text_IO.Put_Paragraph - ("Alire needs some user information to initialize the crate" - & " author and maintainer, for eventual submission to" - & " the Alire community index. This information will be" - & " interactively requested now."); - TIO.New_Line; - TIO.Put_Line - ("You can edit this information at any time with 'alr config'"); - TIO.New_Line; - end if; - Query_Crate_Name (Args, Info); if Cmd.Bin then @@ -616,11 +624,30 @@ package body Alr.Commands.Init is Query_Description (Info); -- Query User info + if User_Not_Already_Configured then + TIO.New_Line; + AAA.Text_IO.Put_Paragraph + ("Alire needs some user information to prepare the crate for " + & "eventual submission to an index, which will be interactively " + & "requested now."); + TIO.New_Line; + TIO.Put_Line + ("You can edit this information at any time with 'alr settings'"); + TIO.New_Line; + end if; Info.Username := To_Unbounded_String (UI.Query_Config.User_Name); - Info.GitHub_Login := To_Unbounded_String - (UI.Query_Config.User_GitHub_Login); + Query_GitHub_Login (Info); Info.Email := To_Unbounded_String (UI.Query_Config.User_Email); + -- Make it clear that the remainder can't be changed with `alr settings` + TIO.New_Line; + if User_Not_Already_Configured then + AAA.Text_IO.Put_Paragraph + ("Alire needs some further crate-specific information to help " + & "other people who want to use your crate."); + end if; + TIO.New_Line; + Query_License (Info); Query_Tags (Info); diff --git a/src/alr/alr-commands-publish.adb b/src/alr/alr-commands-publish.adb index 018669eb5..f94a7369f 100644 --- a/src/alr/alr-commands-publish.adb +++ b/src/alr/alr-commands-publish.adb @@ -29,12 +29,15 @@ package body Alr.Commands.Publish is Options : constant Alire.Publish.All_Options := Alire.Publish.New_Options - (Manifest => + (Manifest => (if Cmd.Manifest.all /= "" then Cmd.Manifest.all else Alire.Roots.Crate_File_Name), - Skip_Build => Cmd.Skip_Build, - Skip_Submit => Cmd.Skip_Submit); + Skip_Build => Cmd.Skip_Build, + Skip_Submit => + -- "--for-private-index" implies "--skip-submit" + Cmd.Skip_Submit or else Cmd.For_Private_Index, + For_Private_Index => Cmd.For_Private_Index); begin if Alire.Utils.Count_True @@ -166,6 +169,13 @@ package body Alr.Commands.Publish is "", "--skip-submit", "Do not create the online pull request onto the community index"); + Define_Switch + (Config, + Cmd.For_Private_Index'Access, + "", "--for-private-index", + "The same as --skip-submit, but additionally disable checks which " + & "are specific to the community index and may not apply to others"); + Define_Switch (Config, Cmd.Cancel'Access, diff --git a/src/alr/alr-commands-publish.ads b/src/alr/alr-commands-publish.ads index 3ca816a4b..9b743c072 100644 --- a/src/alr/alr-commands-publish.ads +++ b/src/alr/alr-commands-publish.ads @@ -55,7 +55,7 @@ package Alr.Commands.Publish is overriding function Usage_Custom_Parameters (Cmd : Command) return String - is ("[--skip-build] [--skip-submit] [--tar] " + is ("[--skip-build] [--skip-submit|--for-private-index] [--tar] " & "[--manifest ] [ [commit]]] [--request-review NUM]"); private @@ -70,7 +70,12 @@ private -- Skip the build check Skip_Submit : aliased Boolean := False; - -- Stop after generation instead of asking the user to continue + -- Skip checking user's GitHub account, and stop after manifest + -- generation instead of asking the user to continue + + For_Private_Index : aliased Boolean := False; + -- Skip_Submit, and also disable checks which only apply to the + -- community index Cancel : aliased GNAT.Strings.String_Access := new String'(Unset); -- Number of a PR to prematurely close diff --git a/src/alr/alr-commands-search.adb b/src/alr/alr-commands-search.adb index f5ff17169..61f6feee2 100644 --- a/src/alr/alr-commands-search.adb +++ b/src/alr/alr-commands-search.adb @@ -194,12 +194,12 @@ package body Alr.Commands.Search is -- End of option verification, start of search. First load the index, -- required to look at its entries. - Tab.Append (TTY.Bold ("NAME")); - Tab.Append (TTY.Bold ("STATUS")); - Tab.Append (TTY.Bold ("VERSION")); - Tab.Append (TTY.Bold ("DESCRIPTION")); - Tab.Append (TTY.Bold ("NOTES")); - Tab.Append (TTY.Bold ("MATCHES")); + Tab.Header ("NAME"); + Tab.Header ("STATUS"); + Tab.Header ("VERSION"); + Tab.Header ("DESCRIPTION"); + Tab.Header ("NOTES"); + Tab.Header ("MATCHES"); declare Busy : Simple_Logging.Ongoing := diff --git a/src/alr/alr-commands-skeleton.ads b/src/alr/alr-commands-skeleton.ads index 83ff18c8f..ff2ec9b8c 100644 --- a/src/alr/alr-commands-skeleton.ads +++ b/src/alr/alr-commands-skeleton.ads @@ -9,7 +9,7 @@ package Alr.Commands.Skeleton is type Command is new Commands.Command with private; overriding - function Name (Cmd : Command) return String + function Name (Cmd : Command) return CLIC.Subcommand.Identifier is ("skeleton"); overriding diff --git a/src/alr/alr-commands-test.adb b/src/alr/alr-commands-test.adb index cfe4c35dd..d16d31705 100644 --- a/src/alr/alr-commands-test.adb +++ b/src/alr/alr-commands-test.adb @@ -436,7 +436,7 @@ package body Alr.Commands.Test is -- Not_Empty -- --------------- - procedure Not_Empty (Item : Ada.Directories.Directory_Entry_Type; + procedure Not_Empty (Item : Alire.Any_Path; Stop : in out Boolean) is pragma Unreferenced (Item, Stop); diff --git a/src/alr/alr-commands-toolchain.adb b/src/alr/alr-commands-toolchain.adb index b5f78188b..f84955787 100644 --- a/src/alr/alr-commands-toolchain.adb +++ b/src/alr/alr-commands-toolchain.adb @@ -1,6 +1,4 @@ -with AAA.Table_IO; - with Alire.Settings.Edit; with Alire.Containers; with Alire.Dependencies; @@ -10,7 +8,7 @@ with Alire.Origins.Deployers; with Alire.Releases.Containers; with Alire.Solver; with Alire.Toolchains; -with Alire.Utils; use Alire.Utils; +with Alire.Utils.Tables; with Alire.Utils.TTY; with Alire.Warnings; @@ -18,6 +16,8 @@ with Semantic_Versioning.Extended; package body Alr.Commands.Toolchain is + use Alire.Utils; + package Name_Sets renames Alire.Containers.Crate_Name_Sets; -------------------- @@ -268,7 +268,7 @@ package body Alr.Commands.Toolchain is pragma Unreferenced (Cmd); use Alire; use type Dependencies.Dependency; - Table : AAA.Table_IO.Table; + Table : Tables.Table; begin Alire.Toolchains.Detect_Externals; -- Even if we have selected a non-external toolchain, in this case we @@ -281,10 +281,10 @@ package body Alr.Commands.Toolchain is end if; Table - .Append (TTY.Emph ("CRATE")) - .Append (TTY.Emph ("VERSION")) - .Append (TTY.Emph ("STATUS")) - .Append (TTY.Emph ("NOTES")) + .Header ("CRATE") + .Header ("VERSION") + .Header ("STATUS") + .Header ("NOTES") .New_Row; for Dep of Alire.Toolchains.Available loop @@ -311,7 +311,7 @@ package body Alr.Commands.Toolchain is end if; end loop; - Table.Print; + Table.Print (Always); end List; ------------- diff --git a/src/alr/alr-commands-version.adb b/src/alr/alr-commands-version.adb index f4fd73a32..a89057d9e 100644 --- a/src/alr/alr-commands-version.adb +++ b/src/alr/alr-commands-version.adb @@ -1,4 +1,5 @@ with Alire.Builds; +with Alire.Cache; with Alire.Settings.Edit; with Alire.Directories; with Alire.Index; @@ -37,8 +38,9 @@ package body Alr.Commands.Version is Args : AAA.Strings.Vector) is use Alire; + use Alire.Utils; use all type Alire.Roots.Optional.States; - Table : Alire.Utils.Tables.Table; + Table : Tables.Table; Index_Outcome : Alire.Outcome; Indexes : constant Alire.Index_On_Disk.Loading.Set := Alire.Index_On_Disk.Loading.Find_All @@ -56,66 +58,82 @@ package body Alr.Commands.Version is / Paths.Cache_Folder_Inside_Working_Folder / Paths.Deps_Folder_Inside_Cache_Folder) else Builds.Path); + + --------- + -- Add -- + --------- + + procedure Add (Key : String; Val : String := "") is + use AAA.Strings; + begin + if (Key = "" or else Val = "") and then Tables.Structured_Output then + return; -- Skip cosmetic rows in structured output + end if; + + if Tables.Structured_Output and then Contains (Key, ":") then + Add (Replace (Key, ":", ""), Val); + return; + end if; + + Table.Append (Key).Append (Val).New_Row; + end Add; + begin if Args.Count /= 0 then Reportaise_Wrong_Arguments (Cmd.Name & " doesn't take arguments"); end if; - Table.Append ("APPLICATION").Append ("").New_Row; - Table.Append ("alr version:") - .Append (Alire.Version.Current.Image).New_Row; - Table.Append ("libalire version:") - .Append (Alire.Version.Current.Image).New_Row; - Table.Append ("compilation date:") - .Append (GNAT.Source_Info.Compilation_ISO_Date & " " - & GNAT.Source_Info.Compilation_Time).New_Row; - Table.Append ("compiled with version:") - .Append (GNAT_Version.Version).New_Row; - - Table.Append ("").New_Row; - Table.Append ("CONFIGURATION").New_Row; - Table.Append ("settings folder:") - .Append (Alire.Settings.Edit.Path).New_Row; - Table.Append ("cache folder:") - .Append (Alire.Settings.Edit.Cache_Path).New_Row; - Table.Append ("vault folder:").Append (Paths.Vault.Path).New_Row; - Table.Append ("build folder:").Append (Build_Path).New_Row; - Table.Append ("temp folder:") - .Append (Alire.Platforms.Folders.Temp).New_Row; - Table.Append ("force flag:").Append (Alire.Force'Image).New_Row; - Table.Append ("non-interactive flag:") - .Append (CLIC.User_Input.Not_Interactive'Image).New_Row; - Table.Append ("community index branch:") - .Append (Alire.Index.Community_Branch).New_Row; - Table.Append ("compatible index versions:") - .Append (Alire.Index.Valid_Versions.Image).New_Row; - Table.Append ("indexes folder:") - .Append (Alire.Settings.Edit.Indexes_Directory).New_Row; - Table.Append ("indexes metadata:") - .Append (if Index_Outcome.Success - then "OK" - else "ERROR: " & Index_Outcome.Message).New_Row; + -- Enrich output when using a structured format only + if Alire.Utils.Tables.Structured_Output then + Table.Header ("key").Header ("Value").New_Row; + end if; + + Add ("APPLICATION", ""); + Add ("alr version:", Alire.Version.Current.Image); + Add ("libalire version:", Alire.Version.Current.Image); + Add ("compilation date:", + GNAT.Source_Info.Compilation_ISO_Date & " " + & GNAT.Source_Info.Compilation_Time); + Add ("compiled with version:", GNAT_Version.Version); + + Add (""); + Add ("CONFIGURATION"); + Add ("settings folder:", Alire.Settings.Edit.Path); + Add ("cache folder:", Alire.Cache.Path); + Add ("vault folder:", Paths.Vault.Path); + Add ("build folder:", Build_Path); + Add ("temp folder:", Alire.Platforms.Folders.Temp); + Add ("force flag:", Alire.Force'Image); + Add ("non-interactive flag:", + CLIC.User_Input.Not_Interactive'Image); + Add ("community index branch:", Alire.Index.Community_Branch); + Add ("compatible index versions:", + Alire.Index.Valid_Versions.Image); + Add ("indexes folder:", + Alire.Settings.Edit.Indexes_Directory); + Add ("indexes metadata:", + (if Index_Outcome.Success + then "OK" + else "ERROR: " & Index_Outcome.Message)); for Index of Indexes loop - Table.Append ("index #" - & AAA.Strings.Trim (Index.Priority'Image) & ":") - .Append ("(" & Index.Name & ") " & Index.Origin).New_Row; + Add ("index #" + & AAA.Strings.Trim (Index.Priority'Image) & ":", + "(" & Index.Name & ") " & Index.Origin); end loop; - Table.Append ("toolchain folder:") - .Append (Alire.Toolchains.Path).New_Row; - Table.Append ("toolchain assistant:") - .Append (if Alire.Toolchains.Assistant_Enabled - then "enabled" - else "disabled").New_Row; + Add ("toolchain folder:", Alire.Toolchains.Path); + Add ("toolchain assistant:", + (if Alire.Toolchains.Assistant_Enabled + then "enabled" + else "disabled")); declare I : Positive := 1; begin for Tool of Alire.Toolchains.Tools loop - Table - .Append ("tool #" & AAA.Strings.Trim (I'Image) - & " " & Tool.As_String & ":") - .Append (if Alire.Toolchains.Tool_Is_Configured (Tool) - then Alire.Toolchains.Tool_Milestone (Tool).Image - else "not configured").New_Row; + Add ("tool #" & AAA.Strings.Trim (I'Image) + & " " & Tool.As_String & ":", + (if Alire.Toolchains.Tool_Is_Configured (Tool) + then Alire.Toolchains.Tool_Milestone (Tool).Image + else "not configured")); I := I + 1; end loop; end; @@ -124,47 +142,40 @@ package body Alr.Commands.Version is System_Manager : constant String := Origins.Deployers.System.Executable_Path; begin - Table - .Append ("system package manager:") - .Append (if System_Manager /= "" - then System_Manager - else "not found: " - & (if Origins.Deployers.System.Executable_Name /= "" - then "`" & Origins.Deployers.System.Executable_Name & "`" - else "unknown package manager")) - .New_Row; - Table - .Append ("distro detection disabled:") - .Append (Platforms.Current.Disable_Distribution_Detection'Image) - .New_Row; + Add ("system package manager:", + (if System_Manager /= "" + then System_Manager + else "not found: " + & (if Origins.Deployers.System.Executable_Name /= "" + then "`" & Origins.Deployers.System.Executable_Name & "`" + else "unknown package manager"))); + Add ("distro detection disabled:", + Platforms.Current.Disable_Distribution_Detection'Image); end; - Table.Append ("").New_Row; - Table.Append ("WORKSPACE").New_Row; - - Table.Append ("root status:") - .Append (Root.Status'Image).New_Row; - Table.Append ("root release:") - .Append (case Root.Status is - when Valid => Root.Value.Release.Milestone.Image, - when others => "N/A").New_Row; - Table.Append ("root load error:") - .Append (case Root.Status is - when Broken => Cmd.Optional_Root.Message, - when Valid => "none", - when Outside => "N/A").New_Row; - Table.Append ("root folder:") - .Append (case Root.Status is - when Outside => "N/A", - when Broken => "N/A", - when Valid => Root.Value.Path).New_Row; - Table.Append ("current folder:").Append (Alire.Directories.Current) - .New_Row; - - Table.Append ("").New_Row; - Table.Append ("SYSTEM").New_Row; + Add (""); + Add ("WORKSPACE"); + Add ("root status:", Root.Status'Image); + Add ("root release:", + (case Root.Status is + when Valid => Root.Value.Release.Milestone.Image, + when others => "N/A")); + Add ("root load error:", + (case Root.Status is + when Broken => Cmd.Optional_Root.Message, + when Valid => "none", + when Outside => "N/A")); + Add ("root folder:", + (case Root.Status is + when Outside => "N/A", + when Broken => "N/A", + when Valid => Root.Value.Path)); + Add ("current folder:", Alire.Directories.Current); + + Add (""); + Add ("SYSTEM"); for Prop of Platform.Properties loop - Table.Append (Prop.Key & ":").Append (Prop.Image).New_Row; + Add (Prop.Key & ":", Prop.Image); end loop; Table.Print (Level => Always); diff --git a/src/alr/alr-commands.adb b/src/alr/alr-commands.adb index 9ce783564..acaf016a1 100644 --- a/src/alr/alr-commands.adb +++ b/src/alr/alr-commands.adb @@ -20,9 +20,12 @@ with Alire.Platforms.Current; with Alire.Root; with Alire.Solutions; with Alire.Toolchains; +with Alire.Utils.Did_You_Mean; +with Alire.Utils.Tables; with Alr.Commands.Action; with Alr.Commands.Build; +with Alr.Commands.Cache; with Alr.Commands.Clean; with Alr.Commands.Config; with Alr.Commands.Dev; @@ -82,6 +85,9 @@ package body Alr.Commands is No_TTY : aliased Boolean := False; -- Used to disable control characters in output + Structured_Format : aliased GNAT.OS_Lib.String_Access + := new String'("unset"); + Version_Only : aliased Boolean := False; -- Just display the current version and exit @@ -172,6 +178,13 @@ package body Alr.Commands is "-n", "--non-interactive", "Assume default answers for all user prompts"); + Define_Switch (Config, + Structured_Format'Access, + Long_Switch => "--format?", + Argument => "FORMAT", + Help => + "Use structured output for tables (JSON, TOML)"); + Define_Switch (Config, No_Color'Access, Long_Switch => "--no-color", @@ -467,6 +480,46 @@ package body Alr.Commands is Trace.Debug ("End command line."); end Log_Command_Line; + --------------------------- + -- Set_Structured_Output -- + --------------------------- + + procedure Set_Structured_Output is + use Alire.Utils; + use all type Tables.Formats; + + Format_Str : constant String + := AAA.Strings.Replace (Structured_Format.all, "=", ""); + + function Is_Valid is + new AAA.Enum_Tools.Is_Valid (Tables.Formats); + + function Suggest is + new Alire.Utils.Did_You_Mean.Enum_Suggestion + (Tables.Formats, + Alire.Utils.Did_You_Mean.Upper_Case); + + begin + if Structured_Format.all /= "unset" then + Alire.Utils.Tables.Structured_Output := True; + else + return; + end if; + + if Format_Str /= "" and then not Is_Valid (Format_Str) then + Reportaise_Wrong_Arguments + ("Unknown argument in --format" & Structured_Format.all + & "." & Suggest (Format_Str)); + end if; + + if Format_Str /= "" then + Alire.Utils.Tables.Structured_Output_Format + := Alire.Utils.Tables.Formats'Value (Format_Str); + else + Alire.Utils.Tables.Structured_Output_Format := JSON; + end if; + end Set_Structured_Output; + use all type Alire.Platforms.Operating_Systems; begin @@ -528,6 +581,10 @@ package body Alr.Commands is Ada.Directories.Set_Directory (Command_Line_Chdir_Target_Path.all); end if; + Set_Structured_Output; + + -- End of global switches + Create_Alire_Folders; begin @@ -585,6 +642,14 @@ package body Alr.Commands is OS_Lib.Bailout (1); end if; end; + exception + when Wrong_Command_Arguments => + Trace.Detail ("alr global switches are invalid"); + if Alire.Log_Level = Debug then + raise; + else + OS_Lib.Bailout (1); + end if; end Execute; ------------------------ @@ -677,6 +742,7 @@ begin -- Commands -- Sub_Cmd.Register ("General", new Sub_Cmd.Builtin_Help); + Sub_Cmd.Register ("General", new Cache.Command); Sub_Cmd.Register ("General", new Settings.Command); Sub_Cmd.Register ("General", new Config.Command); Sub_Cmd.Register ("General", new Install.Command); diff --git a/src/alr/alr-files.adb b/src/alr/alr-files.adb index f728fa1c5..855580bec 100644 --- a/src/alr/alr-files.adb +++ b/src/alr/alr-files.adb @@ -1,4 +1,4 @@ -with Ada.Directories; +with Den.Filesystem; package body Alr.Files is @@ -7,19 +7,27 @@ package body Alr.Files is ------------------------- function Locate_Any_GPR_File return Natural is - use Ada.Directories; Candidates : AAA.Strings.Vector; - procedure Check (File : Directory_Entry_Type) is + ----------- + -- Check -- + ----------- + + procedure Check (File : Alire.Any_Path; Stop : in out Boolean) is + use AAA.Strings; begin - Candidates.Append (Full_Name (File)); + Stop := False; + if Den.Kind (File) in Den.File + and then Has_Suffix (To_Lower_Case (File), ".gpr") + then + Candidates.Append (Den.Filesystem.Full (File)); + end if; end Check; begin - Search (Current_Directory, - "*.gpr", - (Ordinary_File => True, others => False), - Check'Access); + Alire.Directories.Traverse_Tree + (Alire.Directories.Current, + Check'Access); return Natural (Candidates.Length); end Locate_Any_GPR_File; diff --git a/src/alr/alr-utils.adb b/src/alr/alr-utils.adb index 61f88396f..cec9c53b3 100644 --- a/src/alr/alr-utils.adb +++ b/src/alr/alr-utils.adb @@ -6,13 +6,7 @@ package body Alr.Utils is function Contains (V : AAA.Strings.Vector; Subst : String) return Boolean is begin - for Str of V loop - if AAA.Strings.Contains (Str, Subst) then - return True; - end if; - end loop; - - return False; + return (for some Str of V => AAA.Strings.Contains (Str, Subst)); end Contains; end Alr.Utils; diff --git a/testsuite/drivers/alr.py b/testsuite/drivers/alr.py index 18f52109b..c2ac9ae63 100644 --- a/testsuite/drivers/alr.py +++ b/testsuite/drivers/alr.py @@ -123,17 +123,7 @@ def run_alr(*args, **kwargs): argv.insert(1, '-q') argv.extend(args) p = Run(argv) - if (p.status != 0 and complain_on_error) or (p.status == 0 and not complain_on_error): - print('The following command:') - print(' {}'.format(' '.join(quote_arg(arg) for arg in argv))) - print('Exited with status code {}'.format(p.status)) - print('Output:') - print(p.out) - if complain_on_error: - raise CalledProcessError('alr returned non-zero status code') - else: - raise CalledProcessError('alr returned zero status code but ' - 'an error was expected') + _report_unexpected_exit_status(p.status, complain_on_error, argv, p.out) # Convert CRLF line endings (Windows-style) to LF (Unix-style). This # canonicalization is necessary to make output comparison work on all @@ -141,7 +131,8 @@ def run_alr(*args, **kwargs): return ProcessResult(p.status, p.out.replace('\r\n', '\n')) -def run_alr_interactive(args: [str], output: [str], input: [str], timeout=5) -> str: +def run_alr_interactive(args: list[str], output: list[str], input: list[str], + timeout=5, complain_on_error=True) -> str: """ NON-WINDOWS-ONLY Run "alr" with the given arguments, feeding it the given input. No other @@ -151,7 +142,13 @@ def run_alr_interactive(args: [str], output: [str], input: [str], timeout=5) -> :param output: List of strings expected to be output by the subprocess. :param input: List of strings to feed to the subprocess's standard input. :param timeout: Timeout in seconds for the subprocess to complete. + :param complain_on_error: If True and the subprocess exits with a non-zero + status code, print information on the standard output (for debugging) + and raise a CalledProcessError (to abort the test). + Conversely if False and the process ends without error, it's presumed + an error was expected and CalledProcessError is raised too. """ + # Check whether on Windows to fail early (revisit if pexpect is updated?) if platform.system() == "Windows": print('SKIP: pexpect unavailable on Windows') @@ -177,12 +174,37 @@ def run_alr_interactive(args: [str], output: [str], input: [str], timeout=5) -> f"{child.before.decode('utf-8')}") # Assert proper output code - assert child.exitstatus == 0, \ - f"Unexpected exit status: {child.exitstatus}\n" + \ - f"Output: {child.before.decode('utf-8')}" + output = child.before.decode('utf-8') + _report_unexpected_exit_status( + child.exitstatus, complain_on_error, ["alr"] + args, output + ) # Return command output with CRLF replaced by LF (as does run_alr) - return child.before.decode('utf-8').replace('\r\n', '\n') + return output.replace('\r\n', '\n') + + +def _report_unexpected_exit_status(exit_status, complain_on_error, args, output): + """ + Report if a command yielded an unexpected exit status. + + If complain_on_error is True and exit_status is non-zero, or if it is False + and exit_status is zero, print the command and its output, then raise a + CalledProcessError. Otherwise, do nothing. + """ + error_occured = (exit_status != 0) + if (error_occured == complain_on_error): + command = " ".join(quote_arg(arg) for arg in args) + print('The following command:') + print(f' {command}') + print(f'Exited with status code {exit_status}') + print('Output:') + print(output) + if complain_on_error: + raise CalledProcessError('alr returned non-zero status code') + else: + raise CalledProcessError( + 'alr returned zero status code but an error was expected' + ) def fixtures_path(*args): @@ -282,7 +304,8 @@ def index_version(): return index_branch().split('-')[1] -def init_local_crate(name="xxx", binary=True, enter=True, update=True): +def init_local_crate(name="xxx", binary=True, enter=True, update=True, + with_maintainer_login=False): """ Initialize a local crate and enter its folder for further testing. @@ -291,16 +314,23 @@ def init_local_crate(name="xxx", binary=True, enter=True, update=True): :param bool binary: Initialize as --bin or --lib :param bool enter: Enter the created crate directory + + :param bool with_maintainer_login: Set the value of the `maintainers-logins` + field of the manifest to `["github-username"]` so that the crate is + valid for submission to the community index. """ run_alr("init", name, "--bin" if binary else "--lib") + os.chdir(name) if update: - os.chdir(name) run_alr("update") - os.chdir("..") - if enter: - os.chdir(name) + if with_maintainer_login: + with open("alire.toml", "a") as f: + f.write('maintainers-logins = ["github-username"]\n') + + if not enter: + os.chdir("..") def alr_workspace_cache(): diff --git a/testsuite/drivers/asserts.py b/testsuite/drivers/asserts.py index 7947044c4..9b6215c90 100644 --- a/testsuite/drivers/asserts.py +++ b/testsuite/drivers/asserts.py @@ -143,3 +143,11 @@ def assert_substring(target: str, text: str): """ assert target in text, \ f"Missing expected string '{target}' in text:\n{text}" + + +def assert_not_substring(target: str, text: str): + """ + Check that a string is not contained in another string + """ + assert target not in text, \ + f"Unexpected string '{target}' in text:\n{text}" diff --git a/testsuite/drivers/builds.py b/testsuite/drivers/builds.py index 48ff323d9..05e6359c7 100644 --- a/testsuite/drivers/builds.py +++ b/testsuite/drivers/builds.py @@ -83,14 +83,9 @@ def sync() -> None: """ Sync the shared build directory """ - # We force the sync by running a build, no matter if it succeeds or not - try: - subprocess.run(["alr", "-q", "-d", "build"] - , stdout=subprocess.DEVNULL - , stderr=subprocess.DEVNULL - ) - except: - pass + run_alr("build", "--stop-after=generation") + return + def sync_builds() -> None: sync() diff --git a/testsuite/drivers/driver/base_driver.py b/testsuite/drivers/driver/base_driver.py index 209252ceb..e87c15f38 100644 --- a/testsuite/drivers/driver/base_driver.py +++ b/testsuite/drivers/driver/base_driver.py @@ -47,6 +47,7 @@ def __init__(self, env: e3.env.Env, test_env: Dict[str, Any]): # Hardcode OSes for osname in ["windows", "linux", "macos"]: self.skip[f"skip_{osname}"] = not getattr(drivers.helpers, f"on_{osname}")() + self.skip[f"skip_unix"] = drivers.helpers.on_windows() @property def test_control_creator(self): diff --git a/testsuite/drivers/helpers.py b/testsuite/drivers/helpers.py index b61403216..b13877498 100644 --- a/testsuite/drivers/helpers.py +++ b/testsuite/drivers/helpers.py @@ -8,6 +8,7 @@ import re import shutil import stat +import sys from subprocess import run from zipfile import ZipFile @@ -293,3 +294,80 @@ def __exit__(self, exc_type, exc_val, exc_tb): import fcntl fcntl.flock(self.lock_file.fileno(), fcntl.LOCK_UN) self.lock_file.close() + + +GIT_WRAPPER_TEMPLATE = """\ +#! /usr/bin/env python +import subprocess, sys +substitution_dict = {substitution_dict} +# Argument substitutions +args = sys.argv[1:] +for key in substitution_dict: + args = [arg.replace(key, substitution_dict[key]) for arg in args] +# Run git +p = subprocess.run(['{actual_git_path}'] + args, capture_output=True) +# Output substitutions +stdout, stderr = p.stdout.decode(), p.stderr.decode() +for key in substitution_dict: + stdout = stdout.replace(substitution_dict[key], key) + stderr = stderr.replace(substitution_dict[key], key) +print(stdout, end="") +print(stderr, file=sys.stderr, end="") +# Exit with appropriate error code +sys.exit(p.returncode) +""" + +class MockGit: + """ + NON-WINDOWS-ONLY + A context manager which mocks the git command with string substitutions. + + The string substitutions are specified by the dictionary substitution_dict. + Every non-overlapping occurrence of each of its keys in a command line + argument is replaced with its corresponding value before being passed to + git. The reverse substitution is applied to git's output. The substitutions + are applied in the order in which they appear in substitution_dict. + + The mocked version of git will be placed in mock_git_dir, which will be + temporarily added to PATH. + """ + + def __init__(self, substitution_dict, mock_git_dir): + self._substitution_dict = substitution_dict + self._mock_git_dir = mock_git_dir + + def __enter__(self): + # Mocking on Windows would require git.exe wrapper + if on_windows(): + print('SKIP: git mocking unavailable on Windows') + sys.exit(0) + + # Create a wrapper script for git + wrapper_script = GIT_WRAPPER_TEMPLATE.format( + substitution_dict=self._substitution_dict, + actual_git_path=shutil.which("git") + ) + # Add the directory to PATH + try: + os.mkdir(self._mock_git_dir) + except FileExistsError: + pass + os.environ["PATH"] = ( + f'{self._mock_git_dir}{os.pathsep}{os.environ["PATH"]}' + ) + # Write the script to the directory + wrapper_descriptor = os.open( + os.path.join(self._mock_git_dir, "git"), + flags=(os.O_WRONLY | os.O_CREAT | os.O_EXCL), + mode=0o764, + ) + with open(wrapper_descriptor, "w") as f: + f.write(wrapper_script) + + def __exit__(self, type, value, traceback): + # Restore PATH + os.environ["PATH"] = os.environ["PATH"].replace( + f'{self._mock_git_dir}{os.pathsep}', '', 1 + ) + # Delete the wrapper script + os.remove(os.path.join(self._mock_git_dir, "git")) diff --git a/testsuite/requirements.txt b/testsuite/requirements.txt index dcaf78ee3..4293dd909 100644 --- a/testsuite/requirements.txt +++ b/testsuite/requirements.txt @@ -1,3 +1,4 @@ docker e3-testsuite pexpect +toml diff --git a/testsuite/run.py b/testsuite/run.py index 15f9370c4..90b8eccef 100755 --- a/testsuite/run.py +++ b/testsuite/run.py @@ -10,6 +10,8 @@ from __future__ import absolute_import, print_function import os.path +import shutil +import subprocess import sys from argparse import ArgumentTypeError @@ -35,6 +37,16 @@ def add_options(self, parser): dest='alr_path', metavar='FILE', help='''Set `alr` binary to run the testsuite against. Defaults to `alr` from project's `bin` directory.''') + def require_executable(self, name): + path = shutil.which(name) + if path is None: + raise FileNotFoundError(f"{name} not found in PATH") + else: + print(f"Testsuite using {name} at {path} with version:", ) + print(subprocess.run([name, '--version'], + stdout=subprocess.PIPE).stdout.decode()) + sys.stdout.flush() + def set_up(self): super().set_up() os.environ['ALR_PATH'] = self.main.args.alr_path @@ -51,6 +63,13 @@ def set_up(self): # during the tests (e.g. submitting a release by accident) os.environ["ALR_TESTSUITE"] = "TRUE" + # Ensure toolchain is in scope, or err early instead of during tests + # Locate gnat and gprbuild in path and report their location + required_executables = ['gnat', 'gprbuild'] + for exe in required_executables: + self.require_executable(exe) + print() + def _alr_path(self, alr_file): alr_path = os.path.abspath(alr_file) if not os.path.isfile(alr_path): diff --git a/testsuite/skels/global-index/test.yaml b/testsuite/skels/global-index/test.yaml index 1a71b1b05..dbc089b83 100644 --- a/testsuite/skels/global-index/test.yaml +++ b/testsuite/skels/global-index/test.yaml @@ -1,5 +1,6 @@ driver: python-script build_mode: both # one of shared, sandboxed, both (default) +control: # Used to disable test depending on one of: (see no-skel/test.yaml) indexes: basic_index: in_fixtures: true diff --git a/testsuite/skels/local-index/test.yaml b/testsuite/skels/local-index/test.yaml index a0ce9ba5e..f791268e6 100644 --- a/testsuite/skels/local-index/test.yaml +++ b/testsuite/skels/local-index/test.yaml @@ -1,5 +1,6 @@ driver: python-script build_mode: both # one of shared, sandboxed, both (default) +control: # Used to disable test depending on one of: (see no-skel/test.yaml) indexes: my_index: in_fixtures: false diff --git a/testsuite/skels/no-index/test.yaml b/testsuite/skels/no-index/test.yaml index 97d4f3d5c..317e4f5a1 100644 --- a/testsuite/skels/no-index/test.yaml +++ b/testsuite/skels/no-index/test.yaml @@ -7,7 +7,9 @@ control: # Used to disable test depending on one of: - [SKIP, "skip_network", "Network-requiring tests disabled"] - [SKIP, "skip_linux", "Test is Linux-only"] - [SKIP, "skip_macos", "Test is macOS-only"] + - [SKIP, "skip_unix", "Test is Unix-only"] - [SKIP, "skip_windows", "Test is Windows-only"] + # These have to be interpreted as: "skip (if not running under) condition" indexes: compiler_only_index: {} # Note that shared builds require a detected compiler to be able to compute diff --git a/testsuite/tests/cache/softlinks/my_index/crate-0.1.0.tgz b/testsuite/tests/cache/softlinks/my_index/crate-0.1.0.tgz new file mode 100644 index 000000000..4e90424e3 Binary files /dev/null and b/testsuite/tests/cache/softlinks/my_index/crate-0.1.0.tgz differ diff --git a/testsuite/tests/cache/softlinks/my_index/index/cr/crate/crate-0.1.0.toml b/testsuite/tests/cache/softlinks/my_index/index/cr/crate/crate-0.1.0.toml new file mode 100644 index 000000000..a4b05f347 --- /dev/null +++ b/testsuite/tests/cache/softlinks/my_index/index/cr/crate/crate-0.1.0.toml @@ -0,0 +1,11 @@ +description = "Example crate" +name = "crate" +version = "0.1.0" +licenses = "GPL-3.0-only" +maintainers = ["example@example.com"] +maintainers-logins = ["mylogin"] +executables=['main'] + +[origin] +url = "file:../../../crate-0.1.0.tgz" +hashes = ["sha256:e246305107429e936610677bf5244d69510341a7df47cfce9e2278c4ad7da947"] diff --git a/testsuite/tests/cache/softlinks/my_index/index/index.toml b/testsuite/tests/cache/softlinks/my_index/index/index.toml new file mode 100644 index 000000000..bad265e4f --- /dev/null +++ b/testsuite/tests/cache/softlinks/my_index/index/index.toml @@ -0,0 +1 @@ +version = "1.1" diff --git a/testsuite/tests/cache/softlinks/test.py b/testsuite/tests/cache/softlinks/test.py new file mode 100644 index 000000000..fd74e9e1f --- /dev/null +++ b/testsuite/tests/cache/softlinks/test.py @@ -0,0 +1,60 @@ +""" +Test proper syncing of softlinks, even bad ones + +This test is Unix-only, as Windows' tar cannot recreate the broken links: + +crate-0.1.0 +└── crate + ├── bin -> subdir/bin + ├── broken -> missing + ├── lib + │ ├── mock.so -> mock.so.0.0 + │ ├── mock.so.0 -> mock.so.0.0 + │ ├── mock.so.0.0 + │ ├── zzz.so -> mock.so + │ └── zzz.so.0 -> mock.so + ├── loop + │ ├── x -> z + │ ├── y -> x + │ └── z -> y + ├── order + │ ├── ab -> b + │ ├── af -> d/f + │ ├── b + │ ├── cb -> b + │ ├── d + │ │ └── f + │ └── zf -> d/f + ├── self -> self + ├── subdir + │ ├── bin + │ │ ├── loop -> ../../subdir + │ │ └── x + │ ├── parent -> .. + │ └── self -> ../subdir + ├── that -> this + └── this -> that + +""" + +import os +import shutil +import drivers.builds as builds +from drivers.alr import alr_with, init_local_crate, run_alr + + +init_local_crate() + +# Make the crate depend on our troublesome crate and ensure syncing +alr_with("crate") +builds.sync() + +# Ensure that a copy has been made to the cache +if builds.are_shared(): + assert os.path.exists(builds.find_dir("crate")) + +# Cleanup +os.chdir("..") +shutil.rmtree("xxx") + +print('SUCCESS') diff --git a/testsuite/tests/cache/softlinks/test.yaml b/testsuite/tests/cache/softlinks/test.yaml new file mode 100644 index 000000000..f0325a232 --- /dev/null +++ b/testsuite/tests/cache/softlinks/test.yaml @@ -0,0 +1,8 @@ +driver: python-script +build_mode: both +control: + - [SKIP, "skip_unix", "Test is Unix-only"] +indexes: + my_index: + in_fixtures: false + compiler_only_index: {} diff --git a/testsuite/tests/cache/summary/test.py b/testsuite/tests/cache/summary/test.py new file mode 100644 index 000000000..238062e47 --- /dev/null +++ b/testsuite/tests/cache/summary/test.py @@ -0,0 +1,52 @@ +""" +Check the basic report of cache use +""" + +import os +import re +from drivers import builds +from drivers.alr import run_alr, init_local_crate, alr_with +from drivers.asserts import assert_eq, assert_match +from drivers.helpers import contents, dir_separator + +s = re.escape(dir_separator()) + +# Default cache status after clean install + +assert_match(f"""\ +Path:.*alr-config{s}cache +Size: 0.0 B +""", +run_alr("cache").out) + +# Compile something with a dependency and there should be something in the +# cache when builds are shared. + +init_local_crate() +alr_with("libhello") +run_alr("build") +p = run_alr("cache") +if builds.are_shared(): + # Something already in the cache + assert_match(r"Path:.*alr-config[/\\]cache\nSize: (?!0.0 B).*\n", p.out) +else: + # Still nothing if no shared cache + assert_match(r"Path:.*alr-config[/\\]cache\nSize: 0.0 B\n", p.out) + +# After installing some toolchain, for sure there should be something in the +# cache, as binaries always go into the cache. + +run_alr("toolchain", "--select", "gnat_native=1", "gprbuild") +try: + p = run_alr("cache") +except: + # Something strange is happening... + + # Print to stderr the type of file + print(f"EXISTS: {os.path.exists('alr-config/cache/toolchains/gprbuild_1.0.0_e3d52b4a/share/gprbuildalr-config/cache/toolchains/gprbuild_1.0.0_e3d52b4a/share/gprbuild')}", + file=os.stderr) + + assert_eq(run_alr("version").out, contents("../alr-config/cache")) +assert_match(r"Path:.*alr-config[/\\]cache\nSize: (?!0.0 B).*\n", p.out) + +print("SUCCESS") diff --git a/testsuite/tests/cache/summary/test.yaml b/testsuite/tests/cache/summary/test.yaml new file mode 100644 index 000000000..4e949dace --- /dev/null +++ b/testsuite/tests/cache/summary/test.yaml @@ -0,0 +1,5 @@ +driver: python-script +build_mode: both +indexes: + gnat_toolchain_index: {} + basic_index: {} diff --git a/testsuite/tests/get/get-not-found/test.py b/testsuite/tests/get/get-not-found/test.py index f59e890b1..82221d88c 100644 --- a/testsuite/tests/get/get-not-found/test.py +++ b/testsuite/tests/get/get-not-found/test.py @@ -10,7 +10,7 @@ p = run_alr('get', 'does_not_exist', complain_on_error=False) assert_eq(1, p.status) -assert_match('.*Crate \[does_not_exist\] does not exist in the index\.\n', +assert_match('.*Crate \[does_not_exist\] does not exist in the index\..*\n', p.out) assert_eq([], glob('does_not_exist*')) diff --git a/testsuite/tests/get/provides/test.py b/testsuite/tests/get/provides/test.py index 86e835e84..b56d9e08a 100644 --- a/testsuite/tests/get/provides/test.py +++ b/testsuite/tests/get/provides/test.py @@ -19,7 +19,7 @@ # Check the error for a truly unknown crate: assert_eq("""\ -ERROR: Crate [unobtanium] does not exist in the index. +ERROR: Crate [unobtanium] does not exist in the index. Use `alr search unobtanium` for similar names. """, run_alr("get", "unobtanium", "--dirname", complain_on_error=False).out) diff --git a/testsuite/tests/index/external-hint/test.py b/testsuite/tests/index/external-hint/test.py index 2436f2a9e..f926d82b9 100644 --- a/testsuite/tests/index/external-hint/test.py +++ b/testsuite/tests/index/external-hint/test.py @@ -2,13 +2,10 @@ Test the hinting with custom text in external definitions """ -from glob import glob - from drivers.alr import distro_is_known, run_alr from drivers.asserts import assert_eq, assert_match import re -import platform # 1st test: directly attempting to retrieve an external (this is doable for # system externals in supported platforms -- never in this test). Depending on diff --git a/testsuite/tests/index/maint-bad-login/my_index/index/he/hello_world/hello_world-0.1.0.toml b/testsuite/tests/index/maint-bad-login/my_index/index/he/hello_world/hello_world-0.1.0.toml index df813e411..4cd12a989 100644 --- a/testsuite/tests/index/maint-bad-login/my_index/index/he/hello_world/hello_world-0.1.0.toml +++ b/testsuite/tests/index/maint-bad-login/my_index/index/he/hello_world/hello_world-0.1.0.toml @@ -3,7 +3,7 @@ name = "hello_world" version = "0.1.0" licenses = "GPL-3.0-only" maintainers = ["Mr. User "] -maintainers-logins = ["mr.user"] +maintainers-logins = [""] [origin] url = "file:." diff --git a/testsuite/tests/index/maint-bad-login/test.py b/testsuite/tests/index/maint-bad-login/test.py index 2e37dd915..38d6acba3 100644 --- a/testsuite/tests/index/maint-bad-login/test.py +++ b/testsuite/tests/index/maint-bad-login/test.py @@ -1,5 +1,5 @@ """ -Test that maintainers provide a plausible GitHub login +Test that maintainers-logins values can't be empty strings """ from drivers.alr import run_alr @@ -10,7 +10,7 @@ complain_on_error=False, debug=False, quiet=True) assert_match( '.*Loading .*hello_world-0.1.0.toml:.*maintainers-logins:.*' - 'maintainers-logins must be a valid GitHub login, but got: mr.user\n', + 'maintainers-logins values must be non-empty\n', p.out) print('SUCCESS') diff --git a/testsuite/tests/init/github-login/test.py b/testsuite/tests/init/github-login/test.py new file mode 100644 index 000000000..90a865c62 --- /dev/null +++ b/testsuite/tests/init/github-login/test.py @@ -0,0 +1,103 @@ +""" +Check optional input of user.github_login setting and maintainers-logins field +""" + +import os +import shutil + +from drivers.alr import run_alr, run_alr_interactive +from drivers.asserts import assert_eq, assert_substring, assert_not_substring + + +USERNAME_PROMPT = ( + r"If you intend to publish this crate to the community index, you will " + r"need a (\r\n|\r|\n)GitHub account with which to submit a pull request, " + r"which can optionally be (\r\n|\r|\n)configured now \(leave blank to " + r"skip\)\.(\r\n|\r|\n)Please enter your GitHub login: \(default: ''\)" +) + + +# `alr init` a crate without specifying a login. The resulting manifest should +# not contain a `maintainers-logins` field, and user.github_login should remain +# unset. +outputs, inputs = zip(*[ + ("Select the kind of crate you want to create", ""), + ("Enter a short description of the crate", ""), + ("Please enter your full name", ""), + (USERNAME_PROMPT, ""), + ("Please enter your email address", ""), + ("Select a software license for the crate", ""), + ("Enter a comma \(','\) separated list of tags", ""), + ("Enter an optional Website URL for the crate", ""), +]) +run_alr_interactive( + ['init', 'xxx'], + output=outputs, + input=inputs, + timeout=3 +) +assert_eq( + "\n", + run_alr("settings", "--global", "user.github_login").out +) +with open(os.path.join("xxx", "alire.toml")) as f: + assert_not_substring('maintainers-logins', f.read()) + +# Clean up for next test +shutil.rmtree("xxx") + +# Check inputs which aren't valid GitHub logins are rejected, then check +# configuring a valid login. The configured login should appear in the manifest +# file, and in the output of `alr settings` under `user.github_login`. +outputs, inputs = zip(*[ + ("Select the kind of crate you want to create", "" ), + ("Enter a short description of the crate", "" ), + ("Please enter your full name", "" ), + (USERNAME_PROMPT, "invalid_for_GitHub"), + (r"Invalid answer.[\r\n]+Please enter your GitHub", "valid-user-name" ), + ("Please enter your email address", "" ), + ("Select a software license for the crate", "" ), + ("Enter a comma \(','\) separated list of tags", "" ), + ("Enter an optional Website URL for the crate", "" ), +]) +run_alr_interactive( + ['init', 'xxx'], + output=outputs, + input=inputs, + timeout=3 +) +assert_eq( + "user.github_login=valid-user-name\n", + run_alr("settings", "--global", "user.github_login").out +) +with open(os.path.join("xxx", "alire.toml")) as f: + assert_substring('maintainers-logins = ["valid-user-name"]', f.read()) +shutil.rmtree("xxx") + +# Now that a username has been configured, check that the prompt is skipped +# and user.github_login is used instead. +outputs, inputs = zip(*[ + ("Select the kind of crate you want to create", ""), + ("Enter a short description of the crate", ""), + ("Please enter your full name", ""), + ("Please enter your email address", ""), + ("Select a software license for the crate", ""), + ("Enter a comma \(','\) separated list of tags", ""), + ("Enter an optional Website URL for the crate", ""), +]) +run_alr_interactive( + ['init', 'xxx'], + output=outputs, + input=inputs, + timeout=3 +) +assert_eq( + "user.github_login=valid-user-name\n", + run_alr("settings", "--global", "user.github_login").out +) +with open(os.path.join("xxx", "alire.toml")) as f: + assert_substring('maintainers-logins = ["valid-user-name"]', f.read()) +shutil.rmtree("xxx") + + +print('SUCCESS') diff --git a/testsuite/tests/init/github-login/test.yaml b/testsuite/tests/init/github-login/test.yaml new file mode 100644 index 000000000..32c747b3f --- /dev/null +++ b/testsuite/tests/init/github-login/test.yaml @@ -0,0 +1 @@ +driver: python-script diff --git a/testsuite/tests/install/softlinks/test.py b/testsuite/tests/install/softlinks/test.py index 54a23b002..6a06b6460 100644 --- a/testsuite/tests/install/softlinks/test.py +++ b/testsuite/tests/install/softlinks/test.py @@ -1,6 +1,8 @@ """ Test that binary files containing softlinks can be installed properly. The test -crate contains all kinds of pernicious links (broken, recursive, etc.): +crate contains all kinds of pernicious links (broken, recursive, etc.). + +This test is Unix-only, as Windows' tar cannot recreate the broken links: crate/ ├── bin -> subdir/bin @@ -31,10 +33,8 @@ import os import shutil import subprocess -import sys - -from drivers.alr import crate_dirname, run_alr -from drivers.helpers import contents, on_windows +from drivers.alr import run_alr, crate_dirname +from drivers.helpers import contents def kind(file): @@ -45,11 +45,6 @@ def ls(path): return out.stdout -# Does not apply to Windows as it does not support softlinks -if on_windows(): - print('SKIP: on Windows, unapplicable') - sys.exit(0) - # This command should succeed normally run_alr("install", "--prefix=install", "crate") @@ -83,5 +78,6 @@ def ls(path): # Cleanup os.chdir("..") shutil.rmtree(cratedir) +shutil.rmtree("install") print('SUCCESS') diff --git a/testsuite/tests/install/softlinks/test.yaml b/testsuite/tests/install/softlinks/test.yaml index 0a859639c..58bf7be7d 100644 --- a/testsuite/tests/install/softlinks/test.yaml +++ b/testsuite/tests/install/softlinks/test.yaml @@ -1,4 +1,6 @@ driver: python-script +control: + - [SKIP, "skip_unix", "Test is Unix-only"] indexes: my_index: in_fixtures: false diff --git a/testsuite/tests/misc/dir-traversal/test.py b/testsuite/tests/misc/dir-traversal/test.py new file mode 100644 index 000000000..408943982 --- /dev/null +++ b/testsuite/tests/misc/dir-traversal/test.py @@ -0,0 +1,25 @@ +""" +Check that broken/recursive symlinks don't cause alr to fail +""" + +import os +from drivers.alr import run_alr, init_local_crate +# from drivers.asserts import assert_eq, assert_match + +init_local_crate() + +# Create a symbolic link to itself. This used to cause alr to fail. +os.symlink("self", "self") + +# Commands that traverse looking for things (crates, executables) shouldn't +# fail. + +run_alr("clean", "--temp") +run_alr("run") +run_alr("run", "--list") +run_alr("show", "--nested") + +# Remove the symlink, otherwise it breaks the testsuite driver +os.unlink("self") + +print("SUCCESS") \ No newline at end of file diff --git a/testsuite/tests/misc/dir-traversal/test.yaml b/testsuite/tests/misc/dir-traversal/test.yaml new file mode 100644 index 000000000..9a541fdd1 --- /dev/null +++ b/testsuite/tests/misc/dir-traversal/test.yaml @@ -0,0 +1,6 @@ +driver: python-script +build_mode: both +control: + - [SKIP, "skip_unix", "Test is Unix-only"] +indexes: + compiler_only_index: {} diff --git a/testsuite/tests/misc/structured-tables/test.py b/testsuite/tests/misc/structured-tables/test.py new file mode 100644 index 000000000..e4d7da955 --- /dev/null +++ b/testsuite/tests/misc/structured-tables/test.py @@ -0,0 +1,72 @@ +""" +Verify structured output of tables +""" + +import json + +import toml +from drivers.alr import run_alr, init_local_crate +from drivers.asserts import assert_eq, assert_match + +# Check a few commands that output tables + +# Listing of crates +assert_eq("""\ +[ + { + "name": "gnat_external", + "description": "GNAT is a compiler for the Ada programming language" + }, + { + "name": "gprbuild", + "description": "Fake gprbuild external" + } +] +""", + run_alr("--format=JSON", "search", "--crates").out) + +assert_eq("""\ +[[data]] +description = "GNAT is a compiler for the Ada programming language" +name = "gnat_external" +[[data]] +description = "Fake gprbuild external" +name = "gprbuild" + +""", + run_alr("--format=TOML", "search", "--crates").out) + +# Empty pin list + +init_local_crate() +assert_eq("""\ +[ +] +""", + run_alr("--format=JSON", "pin").out) + +assert_eq("""\ + +""", + run_alr("--format=TOML", "pin").out) + +# Check that objects can be reconstructed and queried from the output + +for fmt in ["JSON", "TOML"]: + # List of releases + out = run_alr(f"--format={fmt}", "-q", "search", "--list", "--external").out + # Load and adjust according to format + if fmt == "TOML": + releases = toml.loads(out)["data"] + # In the TOML case, top-level is always a table with a single key "data" + else: + releases = json.loads(out) + + # Check that it is a list of the expected length + assert_eq(list, type(releases)) + assert_eq(2, len(releases)) + # Check some data + assert_eq("gnat_external", releases[0]["name"]) + + +print("SUCCESS") diff --git a/testsuite/tests/misc/structured-tables/test.yaml b/testsuite/tests/misc/structured-tables/test.yaml new file mode 100644 index 000000000..702010525 --- /dev/null +++ b/testsuite/tests/misc/structured-tables/test.yaml @@ -0,0 +1,4 @@ +driver: python-script +build_mode: both +indexes: + compiler_only_index: {} diff --git a/testsuite/tests/monorepo/basic/test.py b/testsuite/tests/monorepo/basic/test.py index 4d5137170..e3c1bc0d7 100644 --- a/testsuite/tests/monorepo/basic/test.py +++ b/testsuite/tests/monorepo/basic/test.py @@ -16,7 +16,7 @@ start_dir = os.getcwd() os.mkdir("monoproject.upstream") os.chdir("monoproject.upstream") -init_local_crate("mycrate", enter=False) +init_local_crate("mycrate", enter=False, with_maintainer_login=True) os.chdir(start_dir) commit = init_git_repo("monoproject.upstream") diff --git a/testsuite/tests/monorepo/doubly-nested/test.py b/testsuite/tests/monorepo/doubly-nested/test.py index 6995e796a..5f646800e 100644 --- a/testsuite/tests/monorepo/doubly-nested/test.py +++ b/testsuite/tests/monorepo/doubly-nested/test.py @@ -15,8 +15,8 @@ index_dir = os.path.join(os.getcwd(), "my_index") os.mkdir("monoproject.upstream") os.chdir("monoproject.upstream") -init_local_crate("myparent") -init_local_crate("mychild") +init_local_crate("myparent", with_maintainer_login=True) +init_local_crate("mychild", with_maintainer_login=True) os.chdir(start_dir) commit = init_git_repo("monoproject.upstream") diff --git a/testsuite/tests/monorepo/manifest-in-place/test.py b/testsuite/tests/monorepo/manifest-in-place/test.py index b6c2e129f..59e2c7388 100644 --- a/testsuite/tests/monorepo/manifest-in-place/test.py +++ b/testsuite/tests/monorepo/manifest-in-place/test.py @@ -17,7 +17,7 @@ start_dir = os.getcwd() os.mkdir("monoproject.upstream") os.chdir("monoproject.upstream") -init_local_crate("crate1", enter=False) +init_local_crate("crate1", enter=False, with_maintainer_login=True) os.chdir(start_dir) commit1 = init_git_repo("monoproject.upstream") diff --git a/testsuite/tests/monorepo/multi-commit/test.py b/testsuite/tests/monorepo/multi-commit/test.py index 344948c85..3ad5a61c9 100644 --- a/testsuite/tests/monorepo/multi-commit/test.py +++ b/testsuite/tests/monorepo/multi-commit/test.py @@ -16,7 +16,7 @@ start_dir = os.getcwd() os.mkdir("monoproject.upstream") os.chdir("monoproject.upstream") -init_local_crate("crate1", enter=False) +init_local_crate("crate1", enter=False, with_maintainer_login=True) os.chdir(start_dir) commit1 = init_git_repo("monoproject.upstream") @@ -32,7 +32,7 @@ # We create a second crate at another commit os.chdir(os.path.join(start_dir, "monoproject.upstream")) -init_local_crate("crate2", enter=False) +init_local_crate("crate2", enter=False, with_maintainer_login=True) os.chdir(start_dir) commit2 = commit_all("monoproject.upstream") diff --git a/testsuite/tests/monorepo/subdir-in-tar/test.py b/testsuite/tests/monorepo/subdir-in-tar/test.py index 686a2bba5..460449499 100644 --- a/testsuite/tests/monorepo/subdir-in-tar/test.py +++ b/testsuite/tests/monorepo/subdir-in-tar/test.py @@ -11,7 +11,7 @@ import os # Prepare our "remote" repo -init_local_crate("xxx", enter=True) +init_local_crate("xxx", enter=True, with_maintainer_login=True) # Publish it. We need to give input to alr, so we directly call it. We use the # generated location as the "online" location, and this works because we are diff --git a/testsuite/tests/pin/branch-remote-protocols/test.py b/testsuite/tests/pin/branch-remote-protocols/test.py new file mode 100644 index 000000000..702bd22b3 --- /dev/null +++ b/testsuite/tests/pin/branch-remote-protocols/test.py @@ -0,0 +1,68 @@ +""" +Check pinning to branches with "git+ssh://" and "xyz+https://" urls +""" + +import os +import shutil +import subprocess + +from drivers.alr import alr_pin, alr_unpin, init_local_crate +from drivers.helpers import init_git_repo, git_branch, MockGit +from drivers.asserts import assert_eq + + +# Create a crate with differing branches. +init_local_crate(name="remote", enter=False) +remote_path = os.path.join(os.getcwd(), "remote") +# On the default branch, test_file contains "This is the main branch.\n". +test_file_path = os.path.join(remote_path, "test_file") +with open(test_file_path, "w") as f: + f.write("This is the main branch.\n") +init_git_repo("remote") +os.chdir("remote") +default_branch = git_branch() +# On the "other" branch, test_file contains "This is the other branch.\n". +subprocess.run(["git", "checkout", "-b", "other"]).check_returncode() +with open(test_file_path, "w") as f: + f.write("This is the other branch.\n") +subprocess.run(["git", "add", "test_file"]).check_returncode() +subprocess.run(["git", "commit", "-m", "Change test_file"]).check_returncode() +# Return to the default branch +subprocess.run(["git", "checkout", default_branch]).check_returncode() +os.chdir("..") + + +# Perform the actual tests +urls = [ + "git+ssh://ssh.gitlab.company-name.com/path/to/repo.git", + "xyz+https://github.com/path/to/repo.git", +] +sanitised_urls = [ + "ssh://ssh.gitlab.company-name.com/path/to/repo.git", + "https://github.com/path/to/repo.git", +] +cache_test_file_path = "alire/cache/pins/remote/test_file" +mocked_git_dir = os.path.join(os.getcwd(), "mock_path") +for url, s_url in zip(urls, sanitised_urls): + # Mock git with a wrapper that naively converts the url into the local path + # to the "remote" crate. + with MockGit({s_url: remote_path}, mocked_git_dir): + # Create an empty crate, and pin the default branch of the test repo + init_local_crate() + alr_pin("remote", url=url, branch=default_branch) + with open(cache_test_file_path) as f: + assert_eq("This is the main branch.\n", f.read()) + + # Edit pin to point to the other branch, and verify the cached copy changes + # as it should + alr_unpin("remote", update=False) + alr_pin("remote", url=url, branch="other") + with open(cache_test_file_path) as f: + assert_eq("This is the other branch.\n", f.read()) + + # Clean up for next test + os.chdir("..") + shutil.rmtree("xxx") + + +print("SUCCESS") diff --git a/testsuite/tests/pin/branch-remote-protocols/test.yaml b/testsuite/tests/pin/branch-remote-protocols/test.yaml new file mode 100644 index 000000000..32c747b3f --- /dev/null +++ b/testsuite/tests/pin/branch-remote-protocols/test.yaml @@ -0,0 +1 @@ +driver: python-script diff --git a/testsuite/tests/pin/branch/test.py b/testsuite/tests/pin/branch/test.py index 53d2806c9..ad580c1f6 100644 --- a/testsuite/tests/pin/branch/test.py +++ b/testsuite/tests/pin/branch/test.py @@ -3,14 +3,12 @@ """ from drivers.alr import run_alr, alr_pin, alr_unpin, init_local_crate -from drivers.asserts import assert_eq, assert_match -from drivers.helpers import git_branch, git_head, init_git_repo +from drivers.asserts import assert_match +from drivers.helpers import git_branch, init_git_repo from e3.os.fs import touch -from re import escape import re import os -import shutil import subprocess # "remote" is going to be the remote crate diff --git a/testsuite/tests/publish/bad-arguments/test.py b/testsuite/tests/publish/bad-arguments/test.py index 4c6f13b7b..723013038 100644 --- a/testsuite/tests/publish/bad-arguments/test.py +++ b/testsuite/tests/publish/bad-arguments/test.py @@ -34,6 +34,16 @@ assert_match(".*invalid git commit id, 40 digits hexadecimal expected.*", p.out) +# Bad commit characters +p = run_alr("publish", "git+http://github.com/repo", "_"*40, + complain_on_error=False) +assert_match(".*invalid git commit id, 40 digits hexadecimal expected.*", + p.out) +p = run_alr("publish", "hg+http://host.name/repo", "_"*40, + complain_on_error=False) +assert_match(".*invalid mercurial commit id, 40 digits hexadecimal expected.*", + p.out) + # VCS without transport or extension p = run_alr("publish", "http://somehost.com/badrepo", "deadbeef", complain_on_error=False) diff --git a/testsuite/tests/publish/check-pre-release-version/test.py b/testsuite/tests/publish/check-pre-release-version/test.py index 58f223f4e..1b257f626 100644 --- a/testsuite/tests/publish/check-pre-release-version/test.py +++ b/testsuite/tests/publish/check-pre-release-version/test.py @@ -6,7 +6,7 @@ from drivers.asserts import assert_match from drivers.helpers import init_git_repo -init_local_crate("my_crate") +init_local_crate("my_crate", with_maintainer_login=True) p = run_alr("publish", "--tar", complain_on_error=False, quiet=False) diff --git a/testsuite/tests/publish/local-repo-branched/test.py b/testsuite/tests/publish/local-repo-branched/test.py index b2d09efcc..340c85dd9 100644 --- a/testsuite/tests/publish/local-repo-branched/test.py +++ b/testsuite/tests/publish/local-repo-branched/test.py @@ -3,6 +3,7 @@ """ from drivers.alr import init_local_crate, run_alr +from drivers.asserts import assert_match from drivers.helpers import init_git_repo from shutil import copyfile from subprocess import run @@ -10,7 +11,7 @@ import os # Prepare our "remote" repo -init_local_crate("xxx", enter=False) +init_local_crate("xxx", enter=False, with_maintainer_login=True) head_commit = init_git_repo("xxx") # Clone to a "local" repo and set minimal config @@ -27,6 +28,12 @@ assert run(["git", "push", "-u", "origin", "devel"]).returncode == 0 # Check that the publishing assistant completes without complaining -run_alr("--force", "publish", "--skip-submit") +p = run_alr("--force", "publish", "--skip-submit", quiet=False) + +# Check the user is warned that the origin URL is a local path +assert_match( + r".*The origin must be a definitive remote location, but is .*", + p.out +) print('SUCCESS') diff --git a/testsuite/tests/publish/local-repo-nonstd/test.py b/testsuite/tests/publish/local-repo-nonstd/test.py index 355cd7fd4..157ccd19e 100644 --- a/testsuite/tests/publish/local-repo-nonstd/test.py +++ b/testsuite/tests/publish/local-repo-nonstd/test.py @@ -20,7 +20,7 @@ def verify_manifest(): # Prepare our "remote" repo, changing the manifest name to "xxx.toml" -init_local_crate("xxx") +init_local_crate("xxx", with_maintainer_login=True) os.rename("alire.toml", "xxx.toml") os.chdir("..") head_commit = init_git_repo("xxx") diff --git a/testsuite/tests/publish/local-repo/test.py b/testsuite/tests/publish/local-repo/test.py index 41d27e43b..6ff3b40cd 100644 --- a/testsuite/tests/publish/local-repo/test.py +++ b/testsuite/tests/publish/local-repo/test.py @@ -20,7 +20,7 @@ def verify_manifest(): # Prepare our "remote" repo -init_local_crate("xxx", enter=False) +init_local_crate("xxx", enter=False, with_maintainer_login=True) head_commit = init_git_repo("xxx") # Clone to a "local" repo and set minimal config diff --git a/testsuite/tests/publish/pin-removal/test.py b/testsuite/tests/publish/pin-removal/test.py index 9956fdad1..f7ee1df27 100644 --- a/testsuite/tests/publish/pin-removal/test.py +++ b/testsuite/tests/publish/pin-removal/test.py @@ -16,7 +16,7 @@ # We create a repository with the nested crate that will act as the upstream # remote repository: start_dir = os.getcwd() -init_local_crate(crate) +init_local_crate(crate, with_maintainer_login=True) # And add the pin directly in the remote alr_pin("unobtanium", path="/") diff --git a/testsuite/tests/publish/private-indexes/my_index/crates/crate/alire.toml b/testsuite/tests/publish/private-indexes/my_index/crates/crate/alire.toml new file mode 100644 index 000000000..f52a77781 --- /dev/null +++ b/testsuite/tests/publish/private-indexes/my_index/crates/crate/alire.toml @@ -0,0 +1,9 @@ +name = "crate" +description = "Dummy crate" +version = "0.0.0" + +authors = ["Your Name"] +maintainers = ["Your Name "] +maintainers-logins = ["github-username"] +website = "" +tags = [] diff --git a/testsuite/tests/publish/private-indexes/my_index/crates/crate/crate.gpr b/testsuite/tests/publish/private-indexes/my_index/crates/crate/crate.gpr new file mode 100644 index 000000000..29bbd90b8 --- /dev/null +++ b/testsuite/tests/publish/private-indexes/my_index/crates/crate/crate.gpr @@ -0,0 +1,22 @@ +with "config/crate_config.gpr"; +project Crate is + + for Source_Dirs use ("src/", "config/"); + for Object_Dir use "obj/" & Crate_Config.Build_Profile; + for Create_Missing_Dirs use "True"; + for Exec_Dir use "bin"; + for Main use ("crate.adb"); + + package Compiler is + for Default_Switches ("Ada") use Crate_Config.Ada_Compiler_Switches; + end Compiler; + + package Binder is + for Switches ("Ada") use ("-Es"); -- Symbolic traceback + end Binder; + + package Install is + for Artifacts (".") use ("share"); + end Install; + +end Crate; diff --git a/testsuite/tests/publish/private-indexes/my_index/crates/crate/src/crate.adb b/testsuite/tests/publish/private-indexes/my_index/crates/crate/src/crate.adb new file mode 100644 index 000000000..27b9f460a --- /dev/null +++ b/testsuite/tests/publish/private-indexes/my_index/crates/crate/src/crate.adb @@ -0,0 +1,4 @@ +procedure Crate is +begin + null; +end Crate; diff --git a/testsuite/tests/publish/private-indexes/my_index/index/cr/crate/crate-1.0.0.toml b/testsuite/tests/publish/private-indexes/my_index/index/cr/crate/crate-1.0.0.toml new file mode 100644 index 000000000..623a83b91 --- /dev/null +++ b/testsuite/tests/publish/private-indexes/my_index/index/cr/crate/crate-1.0.0.toml @@ -0,0 +1,13 @@ +# NOTE: this crate is not used in the test, but we need at least one crate in +# the index or the community index will get cloned due to empty in-memory +# catalog. + +description = "Dummy crate" +name = "crate" +version = "1.0.0" +licenses = [] +maintainers = ["any@bo.dy"] +maintainers-logins = ["someone"] + +[origin] +url = "file:../../../crates/crate" diff --git a/testsuite/tests/publish/private-indexes/my_index/index/index.toml b/testsuite/tests/publish/private-indexes/my_index/index/index.toml new file mode 100644 index 000000000..bad265e4f --- /dev/null +++ b/testsuite/tests/publish/private-indexes/my_index/index/index.toml @@ -0,0 +1 @@ +version = "1.1" diff --git a/testsuite/tests/publish/private-indexes/test.py b/testsuite/tests/publish/private-indexes/test.py new file mode 100644 index 000000000..a21265f43 --- /dev/null +++ b/testsuite/tests/publish/private-indexes/test.py @@ -0,0 +1,391 @@ +""" +Check "alr publish --for-private-index" supports private indexes +""" + + +import os +import shutil +import subprocess + +from drivers.alr import run_alr, run_alr_interactive +from drivers.helpers import init_git_repo, MockGit +from drivers.asserts import assert_match, assert_file_exists + + +INDEX_PATH = os.path.join(os.getcwd(), "my_index", "index") + + +def run(*args): + subprocess.run(*args).check_returncode() + +def test( + args, + url, + num_confirms, + output, + gen_manifest=None, + maint_logins=None, + github_user=None, + expect_success=True +): + """ + Perform the general test procedure. + + - Create a mock remote repo which appears to have a remote URL, and a local + clone thereof. + - `alr init` a crate in this repo + - Run `alr` with the specified arguments, responding `y` to the prompt + `Do you want to proceed with this information?` a specified number of + times + - Assert that `alr`'s final output matches zero or more regex patterns + - Optionally, assert that an index manifest was generated and matches zero + or more regex patterns + + :param list(str) args: The arguments to pass to `alr` (`--no-color` will be + added) + :param str url: The URL at which (as far as Alire is concerned) the remote + repository is located + :param int num_confirms: The number of times to respond `y` to the prompt + `Do you want to proceed with this information?` + :param list(str) output: Zero or more regex patterns which must match the + final output (i.e. that which follows the last confirmation prompt) of + `alr` + :param list(str) gen_manifest: Zero or more regex patterns which must match + the content of the generated manifest. If None, expects no manifest to + be generated. + :param str maint_logins: If not `None`, the value to set for the + `maintainers-logins` field in the crate's manifest before calling `alr` + :param str github_user: If not `None`, the value to set as + `user.github_login` before calling `alr` + :param bool expect_success: If True, the test will fail if `alr` returns a + non-zero exit code. If False, fail on a zero exit code. + """ + # Create an alire workspace to act as a "remote" + os.makedirs("remote") + os.chdir("remote") + run_alr("init", "--bin", "xxx") + os.chdir("xxx") + # Adjust the values of maintainers-logins and user.github_login if required + if github_user is not None: + run(["alr", "settings", "--set", "user.github_login", github_user]) + if maint_logins is not None: + with open("alire.toml", "a") as f: + f.write(f"maintainers-logins = {maint_logins}\n") + # Initialise as a git repo + init_git_repo(".") + remote_path = os.getcwd() + + # Mock git with a wrapper that naively converts the url into the local path + # to the "remote" crate. + mocked_git_dir = os.path.abspath(os.path.join("..", "..", "mocked_git")) + with MockGit({url: remote_path}, mocked_git_dir): + # Create a "local" clone of the "remote" + local_path = os.path.abspath(os.path.join("..", "..", "local", "xxx")) + os.makedirs(local_path) + os.chdir(local_path) + run(["git", "clone", url, local_path]) + + # Run alr + p = run_alr_interactive( + args, + output=num_confirms * [ + "Do you want to proceed with this information?" + ], + input=num_confirms * ["y"], + complain_on_error=expect_success, + timeout=60, + ) + + # Check output matches + for pattern in output: + assert_match(pattern, p) + + # Check the generated manifest file + gen_manifest_path = os.path.join( + os.getcwd(), "alire", "releases", "xxx-0.1.0-dev.toml" + ) + idx_manifest_dir = os.path.join(INDEX_PATH, "xx", "xxx") + os.chdir(os.path.join("..", "..")) + if gen_manifest is None: + assert_file_exists(gen_manifest_path, wanted=False) + else: + # Check existence + assert_file_exists(gen_manifest_path, wanted=True) + + # Check regex matches + with open(gen_manifest_path) as f: + manifest = f.read() + for pattern in gen_manifest: + assert_match(pattern, manifest) + + # Add this manifest to our local index + os.makedirs(idx_manifest_dir) + shutil.copyfile( + gen_manifest_path, + os.path.join(idx_manifest_dir, "xxx-0.1.0-dev.toml") + ) + + # Check that the crate can be retrieved and built without error + p = run_alr("get", "--build", "xxx", quiet=False) + assert_match( + r".*xxx=0\.1\.0-dev successfully retrieved and built.*", + p.out + ) + + # Clean up for next test + shutil.rmtree("local") + shutil.rmtree("remote") + shutil.rmtree(idx_manifest_dir, ignore_errors=True) # may not exist + + +# All tests should behave the same with and without "--force" +for force_arg in ([], ["--force"]): + # A crate suitable for the community index: + # + # Publication should succeed, with either "--for-private-index" or + # "--skip-submit" circumventing the requirement for the user to provide a + # GitHub account with a fork of the community index. + test( + args=force_arg + ["publish", "--skip-submit"], + url="https://github.com/some_user/repo-name.git", + maint_logins='["github-username"]', + num_confirms=2, + output=[ + r".*Success: Your index manifest file has been generated.*", + # Even though the automatic pull request has been skipped, alr + # should provide instructions for submission to the community index. + ( + r".*Please create a pull request against the community index " + r"at https://github.com/alire-project/alire-index including " + r"this file at index/xx/xxx/.*" + ), + ], + gen_manifest=[ + # "git+" should be prepended to avoid ambiguity + r'.*url = "git\+https://github\.com/some_user/repo-name\.git".*', + ], + expect_success=True + ) + test( + args=force_arg + ["publish", "--for-private-index"], + url="https://github.com/some_user/repo-name.git", + maint_logins='["github-username"]', + num_confirms=2, + output=[ + r".*Success: Your index manifest file has been generated.*", + # alr should provide instructions again, but they should be more + # generic, since we don't know where the private index is located. + r".*Please upload this file to the index in the xx/xxx/ subdirectory", + ], + gen_manifest=[ + r'.*url = "git\+https://github\.com/some_user/repo-name\.git".*', + ], + expect_success=True + ) + + # A crate suitable for the community index, with a GitHub user configured: + test( + args=force_arg + ["publish", "--skip-submit"], + url="https://github.com/some_user/repo-name.git", + maint_logins='["github-username"]', + github_user="github-username", + num_confirms=2, + output=[ + r".*Success: Your index manifest file has been generated.*", + # The user has configured a GitHub username, so a specific upload + # URL should be provided + ( + r".*If you haven't already, please fork " + r"https://github.com/alire-project/alire-index to your GitHub.*" + ), + ( + r".*This file can then be uploaded to " + r"https://github\.com/github-username/alire-index/upload/" + r"stable-1\.3\.0/index/xx/xxx to create a pull request against" + r" the community index.*" + ), + ], + gen_manifest=[ + r'.*url = "git\+https://github\.com/some_user/repo-name\.git".*', + ], + expect_success=True + ) + + # A crate unsuitable for the community index because its origin is private: + # + # "alr publish" should fail, because the origin URL looks private (it will + # also fail if the user does not provide a GitHub account with a fork of the + # community index, but that check comes later). + test( + args=force_arg + ["publish"], + url="git@bitbucket.org:/some_user/repo-name.git", + maint_logins='["github-username"]', + num_confirms=1, + output=[ + r".*The remote URL seems to require repository ownership: .*", + ], + gen_manifest=None, + expect_success=False + ) + # "alr publish --skip-submit" will fail for the same reason. + test( + args=force_arg + ["publish", "--skip-submit"], + url="git@bitbucket.org:/some_user/repo-name.git", + maint_logins='["github-username"]', + num_confirms=1, + output=[ + r".*The remote URL seems to require repository ownership: .*", + ], + gen_manifest=None, + expect_success=False + ) + # "alr publish --for-private-index" will succeed. + test( + args=force_arg + ["publish", "--for-private-index"], + url="git@bitbucket.org:/some_user/repo-name.git", + maint_logins='["github-username"]', + num_confirms=2, + output=[ + r".*Success: Your index manifest file has been generated.*", + r".*Please upload this file to the index in the xx/xxx/ subdirectory", + ], + gen_manifest=[ + r'.*url = "git@bitbucket\.org:/some_user/repo-name\.git".*', + ], + expect_success=True + ) + + # A crate unsuitable for the community index because it has a + # "maintainers-logins" value which is invalid for GitHub: + # + # "alr publish" and "alr publish --skip-submit" should fail. + test( + args=force_arg + ["publish"], + url="https://github.com/some_user/repo-name.git", + maint_logins='["valid-for-GitHub", "invalid_for_GitHub"]', + num_confirms=0, # (fails before first confirmation) + output=[ + ( + r".*The maintainer login 'invalid_for_GitHub' " + r"is not a valid GitHub username.*" + ), + ], + gen_manifest=None, + expect_success=False + ) + test( + args=force_arg + ["publish", "--skip-submit"], + url="https://github.com/some_user/repo-name.git", + maint_logins='["valid-for-GitHub", "invalid_for_GitHub"]', + num_confirms=0, + output=[ + ( + r".*The maintainer login 'invalid_for_GitHub' " + r"is not a valid GitHub username.*" + ), + ], + gen_manifest=None, + expect_success=False + ) + # "alr publish --for-private-index" will succeed. + test( + args=force_arg + ["publish", "--for-private-index"], + url="https://github.com/some_user/repo-name.git", + maint_logins='["valid-for-GitHub", "invalid_for_GitHub"]', + num_confirms=2, + output=[ + r".*Success: Your index manifest file has been generated.*", + r".*Please upload this file to the index in the xx/xxx/ subdirectory", + ], + gen_manifest=[ + r'.*url = "git\+https://github\.com/some_user/repo-name\.git".*', + ], + expect_success=True + ) + + # A crate unsuitable for the community index because it has no + # "maintainers-logins" value: + # + # "alr publish" and "alr publish --skip-submit" should fail. + test( + args=force_arg + ["publish"], + url="https://github.com/some_user/repo-name.git", + maint_logins=None, + num_confirms=0, # (fails before first confirmation) + output=[ + r".*Missing required properties: maintainers-logins.*", + ], + gen_manifest=None, + expect_success=False + ) + test( + args=force_arg + ["publish", "--skip-submit"], + url="https://github.com/some_user/repo-name.git", + maint_logins=None, + num_confirms=0, + output=[ + r".*Missing required properties: maintainers-logins.*", + ], + gen_manifest=None, + expect_success=False + ) + # "alr publish --for-private-index" will succeed. + test( + args=force_arg + ["publish", "--for-private-index"], + url="https://github.com/some_user/repo-name.git", + maint_logins=None, + num_confirms=2, + output=[ + r".*Success: Your index manifest file has been generated.*", + r".*Please upload this file to the index in the xx/xxx/ subdirectory", + ], + gen_manifest=[ + r'.*url = "git\+https://github\.com/some_user/repo-name\.git".*', + ], + expect_success=True + ) + + # A crate unsuitable for the community index because "maintainers-logins" + # is an empty list: + # + # This should be identical to there being no "maintainers-logins" field at + # all. + test( + args=force_arg + ["publish"], + url="https://github.com/some_user/repo-name.git", + maint_logins="[]", + num_confirms=0, + output=[ + r".*Missing required properties: maintainers-logins.*", + ], + gen_manifest=None, + expect_success=False + ) + test( + args=force_arg + ["publish", "--skip-submit"], + url="https://github.com/some_user/repo-name.git", + maint_logins="[]", + num_confirms=0, + output=[ + r".*Missing required properties: maintainers-logins.*", + ], + gen_manifest=None, + expect_success=False + ) + test( + args=force_arg + ["publish", "--for-private-index"], + url="https://github.com/some_user/repo-name.git", + maint_logins="[]", + num_confirms=2, + output=[ + r".*Success: Your index manifest file has been generated.*", + r".*Please upload this file to the index in the xx/xxx/ subdirectory", + ], + gen_manifest=[ + r'.*url = "git\+https://github\.com/some_user/repo-name\.git".*', + ], + expect_success=True + ) + + +print("SUCCESS") diff --git a/testsuite/tests/publish/private-indexes/test.yaml b/testsuite/tests/publish/private-indexes/test.yaml new file mode 100644 index 000000000..0a859639c --- /dev/null +++ b/testsuite/tests/publish/private-indexes/test.yaml @@ -0,0 +1,4 @@ +driver: python-script +indexes: + my_index: + in_fixtures: false diff --git a/testsuite/tests/publish/remote-origin-nonstd/test.py b/testsuite/tests/publish/remote-origin-nonstd/test.py index 2651efc2d..b4f6d1f0c 100644 --- a/testsuite/tests/publish/remote-origin-nonstd/test.py +++ b/testsuite/tests/publish/remote-origin-nonstd/test.py @@ -2,7 +2,7 @@ Test proper publishing of a ready remote origin with custom manifest location """ -from drivers.alr import run_alr, index_version +from drivers.alr import init_local_crate, run_alr, index_version from drivers.asserts import assert_match from drivers.helpers import contents, content_of, init_git_repo, zip_dir from shutil import copyfile, rmtree @@ -26,7 +26,7 @@ def verify_manifest(): run_alr("index", "--add", "my_index", "--name", "my_index") # Prepare a repo and a zipball to be used as "remote" targets for publishing -run_alr("init", "--bin", "xxx") +init_local_crate("xxx", enter=False, with_maintainer_login=True) # Rename the manifest location os.rename(os.path.join("xxx", "alire.toml"), os.path.join("xxx", "xxx.toml")) diff --git a/testsuite/tests/publish/remote-origin/test.py b/testsuite/tests/publish/remote-origin/test.py index f35c29510..bdd9ba65f 100644 --- a/testsuite/tests/publish/remote-origin/test.py +++ b/testsuite/tests/publish/remote-origin/test.py @@ -2,7 +2,7 @@ Tests for proper publishing of a ready remote origin """ -from drivers.alr import run_alr, index_version +from drivers.alr import init_local_crate, run_alr, index_version from drivers.asserts import assert_match from drivers.helpers import contents, content_of, init_git_repo, zip_dir from shutil import copyfile, rmtree @@ -26,7 +26,7 @@ def verify_manifest(): run_alr("index", "--add", "my_index", "--name", "my_index") # Prepare a repo and a zipball to be used as "remote" targets for publishing -run_alr("init", "--bin", "xxx") +init_local_crate("xxx", enter=False, with_maintainer_login=True) # Create the zip zip_dir("xxx", "xxx.zip") diff --git a/testsuite/tests/publish/ssh-remote-origin/test.py b/testsuite/tests/publish/ssh-remote-origin/test.py new file mode 100644 index 000000000..3dc33a82a --- /dev/null +++ b/testsuite/tests/publish/ssh-remote-origin/test.py @@ -0,0 +1,34 @@ +""" +Check "alr publish " only allows private origins with --for-private-index +""" + + +from drivers.alr import run_alr +from drivers.asserts import assert_match + + +urls = [ + "git@host.invalid:/path/to/repo.git", +] +commit = "0" * 40 + +# We expect attempts to publish from these origins to fail, because they are +# obviously private. +for url in urls: + p = run_alr("publish", url, commit, complain_on_error=False) + assert_match(r".*The origin cannot use a private remote:.*", p.out) + p = run_alr( + "publish", "--skip-submit", url, commit, complain_on_error=False + ) + assert_match(r".*The origin cannot use a private remote:.*", p.out) + +# Publishing will still fail with "--for-private-index", but it should be due to +# the untrusted host, not because the URLs appear private. +for url in urls: + p = run_alr( + "publish", "--for-private-index", url, commit,complain_on_error=False + ) + assert_match(r".*Origin is hosted on unknown site: host\.invalid.*", p.out) + + +print("SUCCESS") diff --git a/testsuite/tests/publish/ssh-remote-origin/test.yaml b/testsuite/tests/publish/ssh-remote-origin/test.yaml new file mode 100644 index 000000000..32c747b3f --- /dev/null +++ b/testsuite/tests/publish/ssh-remote-origin/test.yaml @@ -0,0 +1 @@ +driver: python-script diff --git a/testsuite/tests/publish/tarball-plaindir-nonstd/test.py b/testsuite/tests/publish/tarball-plaindir-nonstd/test.py index 0b54ebc52..3f48c71f8 100644 --- a/testsuite/tests/publish/tarball-plaindir-nonstd/test.py +++ b/testsuite/tests/publish/tarball-plaindir-nonstd/test.py @@ -11,7 +11,7 @@ import os # Prepare our "remote" repo -init_local_crate("xxx", enter=True) +init_local_crate("xxx", enter=True, with_maintainer_login=True) # with custom manifest location os.rename("alire.toml", "xxx.toml") diff --git a/testsuite/tests/publish/tarball-plaindir/test.py b/testsuite/tests/publish/tarball-plaindir/test.py index a3b910d33..46c956946 100644 --- a/testsuite/tests/publish/tarball-plaindir/test.py +++ b/testsuite/tests/publish/tarball-plaindir/test.py @@ -11,7 +11,7 @@ import os # Prepare our "remote" repo -init_local_crate("xxx", enter=True) +init_local_crate("xxx", enter=True, with_maintainer_login=True) canary = "canary.txt" diff --git a/testsuite/tests/publish/tarball-repo-nonstd/test.py b/testsuite/tests/publish/tarball-repo-nonstd/test.py index ad482832e..93578e115 100644 --- a/testsuite/tests/publish/tarball-repo-nonstd/test.py +++ b/testsuite/tests/publish/tarball-repo-nonstd/test.py @@ -3,6 +3,7 @@ """ from drivers.alr import init_local_crate, run_alr +from drivers.asserts import assert_match from drivers.helpers import init_git_repo from shutil import copyfile from subprocess import run @@ -10,7 +11,7 @@ import os # Prepare our "remote" repo -init_local_crate("xxx", enter=True) +init_local_crate("xxx", enter=True, with_maintainer_login=True) os.rename("alire.toml", "xxx.toml") # Initialize a repo right here @@ -25,11 +26,18 @@ # Publish it. We need to give input to alr, so we directly call it. We use the # generated location as the "online" location, and this works because we are # forcing. ".tgz" is used, as bzip2 is not supported by `git archive`. -p = run(["alr", "-q", "-f", "-n", "publish", "--skip-build", "--skip-submit", "--tar", +p = run(["alr", "-f", "-n", "publish", "--skip-build", "--skip-submit", "--tar", "--manifest", "xxx.toml"], - input=f"file:{os.getcwd()}/alire/archives/xxx-0.1.0-dev.tgz\n".encode()) + input=f"file:{os.getcwd()}/alire/archives/xxx-0.1.0-dev.tgz\n".encode(), + capture_output=True) p.check_returncode() +# Check user is warned that the origin URL is a local path +assert_match( + r".*The origin must be a definitive remote location, but is .*", + p.stderr.decode() +) + # Verify the index manifest has been generated assert os.path.isfile("./alire/releases/xxx-0.1.0-dev.toml") diff --git a/testsuite/tests/publish/tarball-repo/test.py b/testsuite/tests/publish/tarball-repo/test.py index 2dc953bce..aa69bd465 100644 --- a/testsuite/tests/publish/tarball-repo/test.py +++ b/testsuite/tests/publish/tarball-repo/test.py @@ -3,6 +3,7 @@ """ from drivers.alr import init_local_crate, run_alr +from drivers.asserts import assert_match from drivers.helpers import init_git_repo from shutil import copyfile from subprocess import run @@ -10,7 +11,7 @@ import os # Prepare our "remote" repo -init_local_crate("xxx", enter=True) +init_local_crate("xxx", enter=True, with_maintainer_login=True) # Initialize a repo right here init_git_repo(".") @@ -24,10 +25,17 @@ # Publish it. We need to give input to alr, so we directly call it. We use the # generated location as the "online" location, and this works because we are # forcing. ".tgz" is used, as bzip2 is not supported by `git archive`. -p = run(["alr", "-q", "-f", "-n", "publish", "--skip-build", "--skip-submit", "--tar"], - input=f"file:{os.getcwd()}/alire/archives/xxx-0.1.0-dev.tgz\n".encode()) +p = run(["alr", "-f", "-n", "publish", "--skip-build", "--skip-submit", "--tar"], + input=f"file:{os.getcwd()}/alire/archives/xxx-0.1.0-dev.tgz\n".encode(), + capture_output=True) p.check_returncode() +# Check user is warned that the origin URL is a local path +assert_match( + r".*The origin must be a definitive remote location, but is .*", + p.stderr.decode() +) + # Verify the index manifest has been generated assert os.path.isfile("./alire/releases/xxx-0.1.0-dev.toml") diff --git a/testsuite/tests/settings/distro-disable/test.py b/testsuite/tests/settings/distro-disable/test.py index 1e45faa25..3d4912bd8 100644 --- a/testsuite/tests/settings/distro-disable/test.py +++ b/testsuite/tests/settings/distro-disable/test.py @@ -2,11 +2,24 @@ Verify that disabling distro detection works as intended """ -from drivers.alr import run_alr, distro_is_known +from drivers.alr import run_alr, distro_is_known, init_local_crate, alr_with, alr_manifest run_alr("settings", "--global", "--set", "distribution.disable_detection", "true") +# Inspect output of `alr version` assert not distro_is_known(), "Unexpected distro detection" +# Ensure that basic environment can be printed for crates that use +# $DISTRIB_ROOT + +init_local_crate() +# Append usage of DISTRIB_ROOT to the manifest +with open(alr_manifest(), "a") as f: + f.write(""" + [environment] + PATH.append = "${DISTRIB_ROOT}" + """) +run_alr("printenv") + print('SUCCESS') diff --git a/testsuite/tests/settings/distro-disable/test.yaml b/testsuite/tests/settings/distro-disable/test.yaml index 872fc1274..fa855459b 100644 --- a/testsuite/tests/settings/distro-disable/test.yaml +++ b/testsuite/tests/settings/distro-disable/test.yaml @@ -1,3 +1,3 @@ driver: python-script indexes: - basic_index: {} + compiler_only_index: {} diff --git a/testsuite/tests/show/nested/test.py b/testsuite/tests/show/nested/test.py index 90de669f3..98744e3eb 100644 --- a/testsuite/tests/show/nested/test.py +++ b/testsuite/tests/show/nested/test.py @@ -17,7 +17,7 @@ # After entering the crate, it is no longer nested and shouldn't be detected os.chdir("xxx") -assert_match("\s*", +assert_match(r"\s*", run_alr("show", "--nested", quiet=False).out) # If we initialize another crate without entering it, it should again be diff --git a/testsuite/tests/workflows/air-gapping/my_index/crates/hello.tgz b/testsuite/tests/workflows/air-gapping/my_index/crates/hello.tgz new file mode 100644 index 000000000..a39010d27 Binary files /dev/null and b/testsuite/tests/workflows/air-gapping/my_index/crates/hello.tgz differ diff --git a/testsuite/tests/workflows/air-gapping/my_index/crates/libhello.tgz b/testsuite/tests/workflows/air-gapping/my_index/crates/libhello.tgz new file mode 100644 index 000000000..c5882e61a Binary files /dev/null and b/testsuite/tests/workflows/air-gapping/my_index/crates/libhello.tgz differ diff --git a/testsuite/tests/workflows/air-gapping/my_index/index/he/hello/hello-1.0.1.toml b/testsuite/tests/workflows/air-gapping/my_index/index/he/hello/hello-1.0.1.toml new file mode 100644 index 000000000..05221fc60 --- /dev/null +++ b/testsuite/tests/workflows/air-gapping/my_index/index/he/hello/hello-1.0.1.toml @@ -0,0 +1,11 @@ +description = "\"Hello, world!\" demonstration project" +name = "hello" +version = "1.0.1" +maintainers = ["alejandro@mosteo.com", "bob@example.com"] +maintainers-logins = ["mylogin"] + +[[depends-on]] +libhello = "^1.0" + +[origin] +url = "file:../../../crates/hello.tgz" diff --git a/testsuite/tests/workflows/air-gapping/my_index/index/index.toml b/testsuite/tests/workflows/air-gapping/my_index/index/index.toml new file mode 100644 index 000000000..bad265e4f --- /dev/null +++ b/testsuite/tests/workflows/air-gapping/my_index/index/index.toml @@ -0,0 +1 @@ +version = "1.1" diff --git a/testsuite/tests/workflows/air-gapping/my_index/index/li/libhello/libhello-1.0.0.toml b/testsuite/tests/workflows/air-gapping/my_index/index/li/libhello/libhello-1.0.0.toml new file mode 100644 index 000000000..b1d856d2a --- /dev/null +++ b/testsuite/tests/workflows/air-gapping/my_index/index/li/libhello/libhello-1.0.0.toml @@ -0,0 +1,8 @@ +description = "\"Hello, world!\" demonstration project support library" +name = "libhello" +version = "1.0.0" +maintainers = ["alejandro@mosteo.com"] +maintainers-logins = ["mylogin"] + +[origin] +url = "file:../../../crates/libhello.tgz" diff --git a/testsuite/tests/workflows/air-gapping/test.py b/testsuite/tests/workflows/air-gapping/test.py new file mode 100644 index 000000000..396688547 --- /dev/null +++ b/testsuite/tests/workflows/air-gapping/test.py @@ -0,0 +1,83 @@ +""" +Test fetching a crate online, and subsequently building offline. +""" + + +import os +import shutil +import subprocess + +from drivers.alr import run_alr +from drivers.asserts import assert_eq, assert_match + + +# Mock tar, git, curl, gprbuild etc. with dummy scripts +os.mkdir("path-dir") +os.chdir("path-dir") +for executable in ("tar", "git", "hg", "svn", "curl", "gprbuild"): + with open(executable, "w") as f: + f.write("\n".join([ + "#!/usr/bin/env python", + "import sys", + "print('Mocked command called')", + "sys.exit(1)" + ])) + os.chmod(executable, 0o764) +os.environ["PATH"] = f'{os.getcwd()}{os.pathsep}{os.environ["PATH"]}' +os.chdir("..") + + +# Run `alr get hello`. This will fail because tar is unavailable. +p = run_alr("get", "hello", quiet=False, complain_on_error=False) +assert_match(".*Mocked command called", p.out) +assert_match(".*Deployment of path .* to .* failed", p.out) + +# Disable tar mocking and run `alr get hello` to 'download' the crate and its +# dependencies. +os.remove(os.path.join("path-dir", "tar")) +p = run_alr("get", "hello", quiet=False) +assert_match(r".*hello=1\.0\.1 successfully retrieved", p.out) +assert_match(r".*\+ libhello 1\.0\.0 \(new\)", p.out) + +# Re-enable tar mocking and make the index unavailable to simulate disconnection +# from the network +shutil.copy(os.path.join("path-dir", "curl"), os.path.join("path-dir", "tar")) +shutil.move("my_index", "somewhere_else") + +# Simulate transferring to a different system by clearing the alr-config +# directory (we keep settings.toml, since it just does various things to isolate +# the test environment) and changing the absolute path of the crate directory. +for f in os.listdir("alr-config"): + if f != "settings.toml": + shutil.rmtree(os.path.join("alr-config", f)) +shutil.move(f"hello_1.0.1_filesystem", "hello") + +# Run `alr build`. This will fail because gprbuild is unavailable. +os.chdir(f"hello") +p = run_alr("build", quiet=False, complain_on_error=False) +assert_match(".*Mocked command called", p.out) +assert_match(r'.*Command \["gprbuild", .*\] exited with code 1', p.out) + +# Disable gprbuild mocking and run `alr build` to build the crate (with tar +# mocking still enabled to check it doesn't try to fetch anything else) +os.remove(os.path.join("..", "path-dir", "gprbuild")) +p = run_alr("build", quiet=False) + +# Check the built binary works as expected +assert_eq( + b"Hello, world!\n", + subprocess.run([os.path.join("obj", "hello")], capture_output=True).stdout +) + +# Clear out the downloaded dependencies, and verify that `alr build` then +# attempts (and fails) to re-fetch them +shutil.rmtree(os.path.join("alire", "cache", "dependencies")) +p = run_alr("build", quiet=False, complain_on_error=False) +assert_match( + ".*Filesystem crate is neither a folder nor a source archive: ", + p.out +) +assert_match(".*Deployment of path .* to .* failed", p.out) + + +print("SUCCESS") diff --git a/testsuite/tests/workflows/air-gapping/test.yaml b/testsuite/tests/workflows/air-gapping/test.yaml new file mode 100644 index 000000000..ec0413f72 --- /dev/null +++ b/testsuite/tests/workflows/air-gapping/test.yaml @@ -0,0 +1,10 @@ +driver: python-script +# We need 'dependencies.shared=false' anyway for dependencies to be packaged +# inside the workspace, so we only run in sandboxed mode. +build_mode: sandboxed +control: + # Mocking commands this way doesn't work on Windows + - [SKIP, "skip_unix", "Test is Unix-only"] +indexes: + my_index: + in_fixtures: false