├── resources └── glam │ ├── version │ ├── glam.edn │ └── scripts │ ├── glam.cmd │ └── glam.sh ├── .circleci ├── script │ ├── deploy │ ├── install-leiningen │ ├── release │ ├── lein │ ├── install-clojure │ ├── tools.deps │ ├── docker │ └── performance └── config.yml ├── .gitignore ├── script ├── test ├── compile └── compile.bat ├── deps.edn ├── appveyor.yml ├── src └── glam │ ├── main.clj │ └── impl │ └── impl.clj ├── test └── glam │ └── main_test.clj ├── README.md └── LICENSE /resources/glam/version: -------------------------------------------------------------------------------- 1 | 0.0.1-SNAPSHOT 2 | -------------------------------------------------------------------------------- /resources/glam/glam.edn: -------------------------------------------------------------------------------- 1 | {:glam/repos 2 | [{:repo/name glam/core 3 | :git/url "https://github.com/glam-pm/packages"}] 4 | :glam/deps {}} 5 | -------------------------------------------------------------------------------- /.circleci/script/deploy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ -z "$CIRCLE_PULL_REQUEST" ] && [ "$CIRCLE_BRANCH" = "master" ] 4 | then 5 | lein deploy clojars 6 | fi 7 | 8 | exit 0; 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cpcache 2 | test-dir 3 | .glam 4 | /glam.edn 5 | glam-bin 6 | classes 7 | glam-0.0.1-SNAPSHOT-windows-amd64.zip 8 | glam-bin.exe 9 | glam-bin.exp 10 | glam-bin.lib 11 | glam-bin.pdb 12 | glam-bin.stripped.pdb 13 | -------------------------------------------------------------------------------- /.circleci/script/install-leiningen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | curl https://raw.githubusercontent.com/technomancy/leiningen/2.9.1/bin/lein > lein 4 | sudo mkdir -p /usr/local/bin/ 5 | sudo mv lein /usr/local/bin/lein 6 | sudo chmod a+x /usr/local/bin/lein 7 | -------------------------------------------------------------------------------- /resources/glam/scripts/glam.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM echo "path before: %PATH%" 3 | set extra_path= 4 | for /f "delims=" %%i in ('glam-bin %*') do set "extra_path=%%i" 5 | if not "%extra_path%"=="" ( 6 | set "PATH=%extra_path%;%PATH%" 7 | ) 8 | REM echo "path after: %PATH%" 9 | set extra_path= 10 | -------------------------------------------------------------------------------- /.circleci/script/release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -rf /tmp/release 4 | mkdir -p /tmp/release 5 | cp glam-bin /tmp/release 6 | VERSION=$(cat resources/glam/version) 7 | 8 | cd /tmp/release 9 | 10 | ## release binary as zip archive 11 | 12 | zip "glam-$VERSION-$GLAM_PLATFORM-amd64.zip" glam-bin 13 | 14 | ## cleanup 15 | 16 | rm glam-bin 17 | -------------------------------------------------------------------------------- /.circleci/script/lein: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | err=0 4 | function _trap_error() { 5 | local exit_code="$1" 6 | if [ "$exit_code" -ne 2 ] && [ "$exit_code" -ne 3 ]; then 7 | (( err |= "$exit_code" )) 8 | fi 9 | } 10 | 11 | trap '_trap_error $?' ERR 12 | trap 'exit $err' SIGINT SIGTERM 13 | 14 | lein clj-kondo --lint "$(lein classpath)" 15 | 16 | exit "$err" 17 | -------------------------------------------------------------------------------- /script/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "$1" == "clean" ] 4 | then 5 | rm -rf test-dir 6 | fi 7 | 8 | export XDG_CONFIG_HOME 9 | mkdir -p test-dir/.config 10 | XDG_CONFIG_HOME=test-dir/.config 11 | 12 | export XDG_DATA_HOME 13 | mkdir -p test-dir/.data 14 | XDG_DATA_HOME=test-dir/.data 15 | 16 | export XDG_CACHE_HOME 17 | mkdir -p test-dir/.cache 18 | XDG_CACHE_HOME=test-dir/.cache 19 | 20 | clojure -M:test 21 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {org.clojure/clojure {:mvn/version "1.10.2-alpha2"}} 2 | :paths ["src" "packages" "resources"] 3 | :aliases {:test {:extra-paths ["test"] 4 | :extra-deps {cognitect-labs/test-runner 5 | {:git/url "https://github.com/cognitect-labs/test-runner" 6 | :sha "cb96e80f6f3d3b307c59cbeb49bb0dcb3a2a780b"}} 7 | :main-opts ["-m" "cognitect.test-runner"]}}} 8 | -------------------------------------------------------------------------------- /.circleci/script/install-clojure: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | install_dir=${1:-/tmp/clojure} 4 | mkdir -p "$install_dir" 5 | cd /tmp 6 | curl -O -sL https://download.clojure.org/install/clojure-tools-1.10.1.708.tar.gz 7 | tar xzf clojure-tools-1.10.1.708.tar.gz 8 | cd clojure-tools 9 | clojure_lib_dir="$install_dir/lib/clojure" 10 | mkdir -p "$clojure_lib_dir/libexec" 11 | cp ./*.jar "$clojure_lib_dir/libexec" 12 | cp deps.edn "$clojure_lib_dir" 13 | cp example-deps.edn "$clojure_lib_dir" 14 | 15 | sed -i -e 's@PREFIX@'"$clojure_lib_dir"'@g' clojure 16 | mkdir -p "$install_dir/bin" 17 | cp clojure "$install_dir/bin" 18 | cp clj "$install_dir/bin" 19 | 20 | cd /tmp 21 | rm -rf clojure-tools-1.10.1.708.tar.gz 22 | rm -rf clojure-tools 23 | echo "Installed clojure to $install_dir/bin" 24 | -------------------------------------------------------------------------------- /script/compile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | mkdir -p classes 6 | clojure -M -e "(compile 'glam.main)" 7 | 8 | if [ -z "$GRAALVM_HOME" ]; then 9 | echo "Please set $GRAALVM_HOME" 10 | exit 1 11 | fi 12 | 13 | "$GRAALVM_HOME/bin/gu" install native-image 14 | 15 | "$GRAALVM_HOME/bin/native-image" \ 16 | -cp "$(clojure -Spath):classes" \ 17 | -H:Name=glam-bin \ 18 | -H:+ReportExceptionStackTraces \ 19 | --initialize-at-build-time \ 20 | "-H:Log=registerResource:" \ 21 | "-H:IncludeResources=glam/.*" \ 22 | "-H:EnableURLProtocols=http,https,jar" \ 23 | "--enable-all-security-services" \ 24 | --report-unsupported-elements-at-runtime \ 25 | --verbose \ 26 | --no-fallback \ 27 | --no-server \ 28 | "-J-Xmx3g" \ 29 | glam.main 30 | -------------------------------------------------------------------------------- /.circleci/script/tools.deps: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | err=0 4 | function _trap_error() { 5 | local exit_code="$1" 6 | if [ "$exit_code" -ne 2 ] && [ "$exit_code" -ne 3 ]; then 7 | (( err |= "$exit_code" )) 8 | fi 9 | } 10 | 11 | trap '_trap_error $?' ERR 12 | trap 'exit $err' SIGINT SIGTERM 13 | 14 | 15 | # Run as local root dependency 16 | rm -rf /tmp/proj 17 | mkdir -p /tmp/proj 18 | cd /tmp/proj 19 | clojure -Sdeps '{:deps {clj-kondo {:local/root "/home/circleci/repo"}}}' \ 20 | -m clj-kondo.main --lint /home/circleci/repo/src /home/circleci/repo/test 21 | 22 | # Run as git dependency 23 | rm -rf /tmp/proj 24 | mkdir -p /tmp/proj 25 | cd /tmp/proj 26 | 27 | github_user=${CIRCLE_PR_USERNAME:-borkdude} 28 | clojure -Sdeps "{:deps {clj-kondo {:git/url \"https://github.com/$github_user/clj-kondo\" :sha \"$CIRCLE_SHA1\"}}}" \ 29 | -m clj-kondo.main --lint /home/circleci/repo/src /home/circleci/repo/test 30 | 31 | exit "$err" 32 | -------------------------------------------------------------------------------- /.circleci/script/docker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eo pipefail 4 | 5 | image_name="borkdude/clj-kondo" 6 | image_tag=$(cat resources/CLJ_KONDO_VERSION) 7 | latest_tag="latest" 8 | 9 | if [[ $image_tag =~ SNAPSHOT$ ]]; then 10 | echo "This is a snapshot version" 11 | snapshot="true" 12 | else 13 | echo "This is a non-snapshot version" 14 | snapshot="false" 15 | fi 16 | 17 | if [ -z "$CIRCLE_PULL_REQUEST" ] && [ "$CIRCLE_BRANCH" = "master" ]; then 18 | echo "Building Docker image $image_name:$image_tag" 19 | echo "$DOCKERHUB_PASS" | docker login -u "$DOCKERHUB_USER" --password-stdin 20 | docker build -t "$image_name" . 21 | docker tag "$image_name:$latest_tag" "$image_name:$image_tag" 22 | # we only update latest when it's not a SNAPSHOT version 23 | if [ "false" = "$snapshot" ]; then 24 | echo "Pushing image $image_name:$latest_tag" 25 | docker push "$image_name:$latest_tag" 26 | fi 27 | # we update the version tag, even if it's a SNAPSHOT version 28 | echo "Pushing image $image_name:$image_tag" 29 | docker push "$image_name:$image_tag" 30 | else 31 | echo "Not publishing Docker image" 32 | fi 33 | 34 | exit 0; 35 | -------------------------------------------------------------------------------- /.circleci/script/performance: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | err=0 4 | function _trap_error() { 5 | local exit_code="$1" 6 | if [ "$exit_code" -ne 2 ] && [ "$exit_code" -ne 3 ]; then 7 | echo "EXIT CODE :( $exit_code" 8 | (( err |= "$exit_code" )) 9 | fi 10 | } 11 | 12 | trap '_trap_error $?' ERR 13 | trap 'exit $err' SIGINT SIGTERM 14 | 15 | 16 | rm -rf performance.txt 17 | echo -e "==== Build initial cache" | tee -a performance.txt 18 | cp="$(clojure -R:cljs -Spath)" 19 | read -r -d '' config <<'EOF' || true 20 | {:linters 21 | {:not-a-function 22 | {:skip-args [clojure.pprint/defdirectives 23 | cljs.pprint/defdirectives 24 | clojure.data.json/codepoint-case]}}} 25 | EOF 26 | 27 | (time ./clj-kondo --lint "$cp" --cache --config "$config" --parallel) 2>&1 | tee -a performance.txt 28 | 29 | echo -e "\n==== Lint a single file (emulate in-editor usage)" | tee -a performance.txt 30 | (time ./clj-kondo --lint src/clj_kondo/impl/core.clj --cache) 2>&1 | tee -a performance.txt 31 | 32 | count=$(find . -name "*.clj*" -type f | wc -l | tr -d ' ') 33 | echo -e "\n==== Launch clj-kondo for each file in project ($count)" | tee -a performance.txt 34 | (time find src -name "*.clj*" -type f -exec ./clj-kondo --lint {} --cache \; ) 2>&1 | tee -a performance.txt 35 | 36 | exit "$err" 37 | -------------------------------------------------------------------------------- /script/compile.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | if "%GRAALVM_HOME%"=="" ( 4 | echo Please set GRAALVM_HOME 5 | exit /b 6 | ) 7 | 8 | set JAVA_HOME=%GRAALVM_HOME% 9 | set PATH=%GRAALVM_HOME%\bin;%PATH% 10 | 11 | set /P GLAM_VERSION=< resources\glam\version 12 | echo Building glam-bin %GLAM_VERSION% 13 | 14 | if "%GRAALVM_HOME%"=="" ( 15 | echo Please set GRAALVM_HOME 16 | exit /b 17 | ) 18 | 19 | set PATH=%USERPROFILE%\deps.clj;%PATH% 20 | 21 | if not exist "classes" mkdir classes 22 | call deps -e "(compile 'glam.main)" 23 | deps -Spath > .classpath 24 | set /P GLAM_CLASSPATH=<.classpath 25 | 26 | call %GRAALVM_HOME%\bin\gu.cmd install native-image 27 | 28 | call %GRAALVM_HOME%\bin\native-image.cmd ^ 29 | "-cp" "%GLAM_CLASSPATH%;classes" ^ 30 | "-H:Name=glam-bin" ^ 31 | "-H:+ReportExceptionStackTraces" ^ 32 | "--initialize-at-build-time" ^ 33 | "-H:IncludeResources=glam/.*" ^ 34 | "-J-Dclojure.spec.skip-macros=true" ^ 35 | "-J-Dclojure.compiler.direct-linking=true" ^ 36 | "--report-unsupported-elements-at-runtime" ^ 37 | "-H:EnableURLProtocols=http,https,jar" ^ 38 | "--enable-all-security-services" ^ 39 | "--verbose" ^ 40 | "--no-fallback" ^ 41 | "--no-server" ^ 42 | "-J-Xmx3g" ^ 43 | "glam.main" 44 | 45 | del .classpath 46 | 47 | if %errorlevel% neq 0 exit /b %errorlevel% 48 | 49 | echo Creating zip archive 50 | jar -cMf glam-%GLAM_VERSION%-windows-amd64.zip glam-bin.exe 51 | -------------------------------------------------------------------------------- /resources/glam/scripts/glam.sh: -------------------------------------------------------------------------------- 1 | export PATH 2 | export GLAM_GLOBAL_PATH 3 | export GLAM_LOCAL_PATH 4 | 5 | GLAM_GLOBAL_PATH="" 6 | GLAM_LOCAL_PATH="" 7 | GLAM_SYS_PATH=$PATH 8 | 9 | glam_global_path() { 10 | if [ -s "$HOME/.glam/path" ] 11 | then 12 | GLAM_GLOBAL_PATH=$(cat "$HOME"/.glam/path) 13 | PATH="$GLAM_GLOBAL_PATH:$PATH" 14 | fi 15 | } 16 | 17 | glam_local_path() { 18 | if [ -s ".glam/path" ] 19 | then 20 | GLAM_LOCAL_PATH=$(cat .glam/path) 21 | PATH="$GLAM_LOCAL_PATH:$PATH" 22 | fi 23 | } 24 | 25 | glam_global_path 26 | glam_local_path 27 | 28 | glam_join_paths() { 29 | for I in "$@" 30 | do 31 | if [ "$I" != "" ] 32 | then 33 | if [ "$out" = "" ] 34 | then 35 | out=$I 36 | else 37 | out=${out:+$out}:$I 38 | fi 39 | fi 40 | done 41 | echo "$out" 42 | } 43 | 44 | glam() { 45 | # shellcheck disable=SC2153 46 | if [ "$GLAM_CMD" != "" ] 47 | then 48 | glam_cmd="$GLAM_CMD" 49 | elif [ -x "$(which glam-bin)" ] 50 | then 51 | glam_cmd="glam-bin" 52 | else 53 | >&2 echo "glam-bin not found, have you installed it and added it to the path?" 54 | return 1 55 | fi 56 | extra_path=$(eval "$glam_cmd" "$@") 57 | glam_global_path 58 | glam_local_path 59 | exit_code=$? 60 | path="$(glam_join_paths "$extra_path" "$GLAM_LOCAL_PATH" "$GLAM_GLOBAL_PATH")" 61 | PATH=$(glam_join_paths "$path" "$GLAM_SYS_PATH") 62 | return $exit_code 63 | } 64 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | version: "v-{build}" 4 | 5 | image: Visual Studio 2017 6 | 7 | clone_folder: C:\projects\glam 8 | 9 | environment: 10 | GRAALVM_HOME: C:\projects\glam\graalvm\graalvm-ce-java11-20.2.0 11 | BABASHKA_XMX: "-J-Xmx5g" 12 | 13 | cache: 14 | - C:\ProgramData\chocolatey\lib -> project.clj, appveyor.yml 15 | - '%USERPROFILE%\.m2 -> project.clj' 16 | - 'graalvm -> appveyor.yml' 17 | 18 | clone_script: 19 | - ps: >- 20 | if(-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { 21 | git clone -q --branch=$env:APPVEYOR_REPO_BRANCH https://github.com/$env:APPVEYOR_REPO_NAME.git $env:APPVEYOR_BUILD_FOLDER 22 | cd $env:APPVEYOR_BUILD_FOLDER 23 | git checkout -qf $env:APPVEYOR_REPO_COMMIT 24 | } else { 25 | git clone -q https://github.com/$env:APPVEYOR_REPO_NAME.git $env:APPVEYOR_BUILD_FOLDER 26 | cd $env:APPVEYOR_BUILD_FOLDER 27 | git fetch -q origin +refs/pull/$env:APPVEYOR_PULL_REQUEST_NUMBER/merge: 28 | git checkout -qf FETCH_HEAD 29 | } 30 | - cmd: git submodule update --init --recursive 31 | 32 | build_script: 33 | - cmd: >- 34 | 35 | # set CLJ_KONDO_TEST_ENV=jvm 36 | 37 | # call script/test.bat 38 | 39 | # see https://github.com/quarkusio/quarkus/pull/7663 40 | 41 | - cmd: >- 42 | call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" 43 | 44 | powershell -Command "if (Test-Path('graalvm')) { return } else { (New-Object Net.WebClient).DownloadFile('https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.2.0/graalvm-ce-java11-windows-amd64-20.2.0.zip', 'graalvm.zip') }" 45 | 46 | powershell -Command "if (Test-Path('graalvm')) { return } else { Expand-Archive graalvm.zip graalvm }" 47 | 48 | powershell -Command "iwr -useb https://raw.githubusercontent.com/borkdude/deps.clj/master/install.ps1 | iex" 49 | 50 | - cmd: >- 51 | call script/compile.bat 52 | 53 | artifacts: 54 | - path: glam-*-windows-amd64.zip 55 | name: glam-bin 56 | -------------------------------------------------------------------------------- /src/glam/main.clj: -------------------------------------------------------------------------------- 1 | (ns glam.main 2 | (:gen-class) 3 | (:require [clojure.string :as str] 4 | [glam.impl.impl :as impl])) 5 | 6 | (defn split-when 7 | "Like partition-by but splits collection only when `pred` returns 8 | a truthy value. E.g. `(split-when odd? [1 2 3 4 5]) => ((1 2) (3 4) (5))`" 9 | [pred coll] 10 | (lazy-seq 11 | (when-let [s (seq coll)] 12 | (let [fst (first s) 13 | f (complement pred) 14 | run (cons fst (take-while #(f %) (next s)))] 15 | (cons run (split-when pred (lazy-seq (drop (count run) s)))))))) 16 | 17 | (def boolean-flags 18 | #{"--force" "--verbose" "--global" "-g"}) 19 | 20 | (def subcommands #{"install" "setup" 21 | "package" "add" "set-current"}) 22 | 23 | (defn parse-args [args] 24 | (->> args 25 | (split-when #(or (str/starts-with? % "-") 26 | (contains? subcommands %))) 27 | (reduce (fn [acc [k & vs]] 28 | (assoc acc k 29 | (or vs 30 | (when (contains? boolean-flags k) 31 | true)))) 32 | {}))) 33 | 34 | (defn main 35 | [& args] 36 | (when-let [subc (first args)] 37 | (let [parsed (parse-args args)] 38 | (case subc 39 | "install" 40 | (let [{:keys [:path :exit]} 41 | (impl/install (get parsed "install") 42 | (boolean (get parsed "--force")) 43 | (boolean (get parsed "--verbose")) 44 | ;; not used anymore: 45 | (boolean (or (get parsed "--global") 46 | (get parsed "-g"))))] 47 | (println path) 48 | exit) 49 | "setup" 50 | (let [parsed (parse-args (cons subc (rest args)))] 51 | (impl/setup (boolean (get parsed "--force")))) 52 | "pull" 53 | (impl/pull-packages) 54 | "package" 55 | (case (second args) 56 | "add" 57 | (impl/package-add (get parsed "add")) 58 | "set-current" 59 | (impl/package-set-current (get parsed "set-current"))) 60 | ;; fallback: 61 | (impl/warn "Unknown command:" subc))))) 62 | 63 | (defn -main [& args] 64 | (let [exit (apply main args)] 65 | (shutdown-agents) 66 | (System/exit (or exit 0)))) 67 | -------------------------------------------------------------------------------- /test/glam/main_test.clj: -------------------------------------------------------------------------------- 1 | (ns glam.main-test 2 | (:require [clojure.java.io :as io] 3 | [clojure.string :as str] 4 | [clojure.test :as t :refer [deftest is testing]] 5 | [glam.main :as main])) 6 | 7 | (deftest setup-test 8 | (with-out-str 9 | (binding [*err* *out*] (main/main "setup")))) 10 | 11 | (defmacro suppress-output [& body] 12 | `(let [sw# (java.io.StringWriter.)] 13 | (binding [*out* sw# 14 | *err* sw#] 15 | ~@body))) 16 | 17 | (deftest shell-install-test 18 | (let [cache-dir (io/file "test-dir" ".cache" ".glam" "repository" "org.babashka" "babashka" "0.2.2") 19 | data-dir (io/file "test-dir" ".data" ".glam" "repository" "org.babashka" "babashka" "0.2.2") 20 | bb-executable (io/file data-dir "bb") 21 | output (with-out-str (main/main "install" "org.babashka/babashka@0.2.2"))] 22 | (is (.exists cache-dir) (str cache-dir " doesn't exist")) 23 | (is (.exists data-dir) (str data-dir " doesn't exist")) 24 | (is (.exists bb-executable)) 25 | (is (.canExecute bb-executable)) 26 | (is (str/includes? output (.getPath data-dir))) 27 | (testing "exit code is zero on install success" 28 | (let [exit (suppress-output (main/main "install" "org.babashka/babashka@0.2.2"))] 29 | (is (zero? exit)))) 30 | (testing "exit code is positive on install failure" 31 | (let [exit (suppress-output (main/main "install" "org.foo/bar"))] 32 | (is (pos? exit)))))) 33 | 34 | (deftest project-install-test 35 | (let [cache-dir (io/file "test-dir" ".cache" ".glam" "repository" "clj-kondo" "clj-kondo" "2020.09.09") 36 | data-dir (io/file "test-dir" ".data" ".glam" "repository" "clj-kondo" "clj-kondo" "2020.09.09") 37 | config-file (io/file "glam.edn") 38 | _ (spit config-file "{:glam/deps {clj-kondo/clj-kondo {:dep/version \"2020.09.09\"}}}") 39 | clj-kondo-executable (io/file data-dir "clj-kondo") 40 | _ (with-out-str (main/main "install"))] 41 | (is (.exists cache-dir) (str cache-dir " doesn't exist")) 42 | (is (.exists data-dir) (str data-dir " doesn't exist")) 43 | (is (.exists clj-kondo-executable)) 44 | (is (.canExecute clj-kondo-executable)) 45 | (is (str/includes? (slurp (io/file ".glam" "path")) (.getPath data-dir))))) 46 | 47 | (deftest install-global-test 48 | ;; TODO: global install test 49 | ;; Check: if installed.edn and path file are correctly set 50 | ;; Btw, we probably also have to update our bash script to account for XDG_CONFIG_HOME and XDG_DATA_HOME 51 | ) 52 | 53 | (deftest config-test 54 | ;; TODO: repositories.edn 55 | ) 56 | 57 | (defn test-ns-hook [] 58 | (setup-test) 59 | (shell-install-test) 60 | (project-install-test)) 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Glam 2 | 3 | A cross-platform package manager. 4 | 5 | Work in progress, still in flux, not ready for production, breaking changes will happen. 6 | 7 | ## Goals 8 | 9 | - Easy CI install of glam itself and the packages that it supports 10 | - Cross platform: support for linux, macOS and Windows (without relying on PowerShell) 11 | - Bring binaries into scope globally or just for one shell or directory 12 | - Configuration using [EDN](https://github.com/edn-format/edn) 13 | 14 | ## Install 15 | 16 | Currently, glam relies on `git` (for downloading package repos) and `tar` 17 | (optional, for untarring `.tgz` archives). 18 | 19 | Navigate to the latest build on 20 | [CircleCI](https://app.circleci.com/pipelines/github/borkdude/glam) and download 21 | a binary for linux or macOS. Binaries for Windows are coming soon. Unzip the 22 | binary, place it on your path and run: 23 | 24 | ``` clojure 25 | $ glam-bin setup 26 | ``` 27 | 28 | This installs a shell helper script and pulls the latest glam [packages](https://github.com/glam-pm/packages). 29 | 30 | To finish setup, add this to your `.bashrc` analog: 31 | 32 | ``` shell 33 | source $HOME/.glam/scripts/glam.sh 34 | ``` 35 | 36 | To immediately start using glam, also execute the above in your shell. 37 | 38 | After setting up, you will find a `glam.edn` in `$HOME/.config/glam` with the following contents: 39 | 40 | ``` clojure 41 | {:glam/repos 42 | [{:repo/name glam/core 43 | :git/url "https://github.com/glam-pm/packages"}] 44 | :glam/deps {}} 45 | ``` 46 | 47 | Sample installation in a fresh Ubuntu docker image: 48 | 49 | ``` shell 50 | $ docker run -it --rm ubuntu /bin/bash 51 | 52 | # installing glam itself 53 | $ apt-get update && apt-get install curl git unzip -y 54 | $ curl -sLO https://30-298997735-gh.circle-artifacts.com/0/release/glam-0.0.1-SNAPSHOT-linux-amd64.zip 55 | $ unzip glam-0.0.1-SNAPSHOT-linux-amd64.zip -d /usr/local/bin 56 | $ glam-bin setup 57 | $ source $HOME/.glam/scripts/glam.sh 58 | 59 | # installing clj-kondo and babashka: 60 | $ glam install clj-kondo/clj-kondo org.babashka/babashka 61 | $ clj-kondo --version 62 | clj-kondo v2020.09.09 63 | $ bb --version 64 | babashka v0.2.2 65 | ``` 66 | 67 | ## Packages 68 | 69 | Package files like `/.glam.edn` are listed in package 70 | repos specified in the global `glam.edn` config file under 71 | `:glam/repos`. Packages are cloned/pulled to `$HOME/.glam/packages`. The main 72 | package repo lives in `$HOME/.glam/packages/glam/core`. You can add your own 73 | repos in the config file and also change precedence by changing the order. 74 | 75 | To update package repos, run: 76 | 77 | ``` clojure 78 | $ glam pull 79 | ``` 80 | 81 | ## Usage 82 | 83 | ## Current shell 84 | 85 | To install packages for the current shell: 86 | 87 | ``` clojure 88 | $ glam install clj-kondo/clj-kondo org.babashka/babashka 89 | ``` 90 | 91 | Now `clj-kondo` and `bb` are available: 92 | 93 | ``` clojure 94 | $ which bb 95 | /Users/borkdude/.glam/repository/org.babashka/babashka/0.2.2/bb 96 | $ which clj-kondo 97 | /Users/borkdude/.glam/repository/clj-kondo/clj-kondo/2020.09.09/clj-kondo 98 | $ bb '(+ 1 2 3)' 99 | 6 100 | ``` 101 | 102 | Use `--verbose` for more output, `--force` for re-downloading packages. 103 | 104 | To install a specific versions: 105 | 106 | ``` clojure 107 | $ glam install clj-kondo/clj-kondo@2020.09.09 org.babashka/babashka@0.2.2 108 | ``` 109 | 110 | ### Project 111 | 112 | To save installation settings for a project directory, create a `glam.edn` with the following contents: 113 | 114 | ``` clojure 115 | {:glam/deps {org.babashka/babashka {:dep/version "0.2.2"}}} 116 | ``` 117 | 118 | To use the latest version, use `{org.babashka/babashka {:dep/version :latest}}`. 119 | 120 | Then run `glam install` and the deps should be added to the path. 121 | 122 | ### Global 123 | 124 | To install packages globally, add to `:glam/deps` in `$HOME/.config/glam/glam.edn`: 125 | 126 | ``` clojure 127 | {org.babashka/babashka {:dep/version "0.2.2}} 128 | ``` 129 | 130 | Run `glam install` and the global package should be added to the path. 131 | 132 | ## Dev 133 | 134 | To develop glam using Clojure, you can invoke it using `clojure -M:glam` when 135 | adding this to `deps.edn`: 136 | 137 | ``` clojure 138 | :glam {:extra-deps 139 | {borkdude/glam {:local/root "/your/dev/glam"}} 140 | :main-opts ["-m" "glam.main"]} 141 | ``` 142 | 143 | You can re-install shell scripts using `clojure -M:glam setup --force`. 144 | 145 | You can override calling the binary in the shell script with `GLAM_CMD`, for example: 146 | 147 | ``` clojure 148 | $ GLAM_CMD="clojure -M:glam" glam install 149 | ``` 150 | 151 | ## License 152 | 153 | Copyright © 2020 Michiel Borkent 154 | 155 | Distributed under the EPL License. See LICENSE. 156 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Clojure CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-clojure/ for more details 4 | # 5 | version: 2.1 6 | jobs: 7 | jvm: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/clojure:lein-2.8.1 11 | working_directory: ~/repo 12 | environment: 13 | LEIN_ROOT: "true" 14 | steps: 15 | - checkout 16 | - restore_cache: 17 | keys: 18 | - v1-dependencies-{{ checksum "deps.edn" }} 19 | # fallback to using the latest cache if no exact match is found 20 | - v1-dependencies- 21 | - run: 22 | name: Install Clojure 23 | command: | 24 | wget -nc https://download.clojure.org/install/linux-install-1.10.1.708.sh 25 | chmod +x linux-install-1.10.1.708.sh 26 | sudo ./linux-install-1.10.1.708.sh 27 | - run: 28 | name: Run JVM tests 29 | command: | 30 | script/test 31 | - save_cache: 32 | paths: 33 | - ~/.m2 34 | key: v1-dependencies-{{ checksum "deps.edn" }} 35 | 36 | linux: 37 | docker: 38 | - image: circleci/clojure:lein-2.8.1 39 | working_directory: ~/repo 40 | environment: 41 | LEIN_ROOT: "true" 42 | GRAALVM_HOME: /home/circleci/graalvm-ce-java11-20.2.0 43 | GLAM_PLATFORM: linux # used in release script 44 | GLAM_TEST_ENV: native 45 | steps: 46 | - checkout 47 | - restore_cache: 48 | keys: 49 | - linux-{{ checksum "deps.edn" }}-{{ checksum ".circleci/config.yml" }} 50 | - run: 51 | name: Install Clojure 52 | command: | 53 | wget https://download.clojure.org/install/linux-install-1.10.1.708.sh 54 | chmod +x linux-install-1.10.1.708.sh 55 | sudo ./linux-install-1.10.1.708.sh 56 | - run: 57 | name: Install native dev tools 58 | command: | 59 | sudo apt-get update 60 | sudo apt-get -y install gcc g++ zlib1g-dev 61 | - run: 62 | name: Download GraalVM 63 | command: | 64 | cd ~ 65 | if ! [ -d graalvm-ce-java11-20.2.0 ]; then 66 | curl -O -sL https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.2.0/graalvm-ce-java11-linux-amd64-20.2.0.tar.gz 67 | tar xzf graalvm-ce-java11-linux-amd64-20.2.0.tar.gz 68 | fi 69 | - run: 70 | name: Build binary 71 | command: | 72 | script/compile 73 | no_output_timeout: 30m 74 | # - run: 75 | # name: Run tests 76 | # command: | 77 | # script/test 78 | - run: 79 | name: Release 80 | command: | 81 | .circleci/script/release 82 | - save_cache: 83 | paths: 84 | - ~/.m2 85 | - ~/graalvm-ce-java11-20.2.0 86 | key: linux-{{ checksum "deps.edn" }}-{{ checksum ".circleci/config.yml" }} 87 | - store_artifacts: 88 | path: /tmp/release 89 | destination: release 90 | mac: 91 | macos: 92 | xcode: "9.4.1" 93 | environment: 94 | GRAALVM_HOME: /Users/distiller/graalvm-ce-java11-20.2.0/Contents/Home 95 | GLAM_PLATFORM: macos # used in release script 96 | # CLJ_KONDO_TEST_ENV: native 97 | steps: 98 | - checkout 99 | - restore_cache: 100 | keys: 101 | - mac-{{ checksum "deps.edn" }}-{{ checksum ".circleci/config.yml" }} 102 | - run: 103 | name: Install Clojure 104 | command: | 105 | .circleci/script/install-clojure /usr/local 106 | - run: 107 | name: Install Leiningen 108 | command: | 109 | .circleci/script/install-leiningen 110 | 111 | - run: 112 | name: Download GraalVM 113 | command: | 114 | cd ~ 115 | ls -la 116 | if ! [ -d graalvm-ce-java11-20.2.0 ]; then 117 | curl -O -sL https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.2.0/graalvm-ce-java11-darwin-amd64-20.2.0.tar.gz 118 | tar xzf graalvm-ce-java11-darwin-amd64-20.2.0.tar.gz 119 | fi 120 | - run: 121 | name: Build binary 122 | command: | 123 | script/compile 124 | no_output_timeout: 30m 125 | # - run: 126 | # name: Run tests 127 | # command: | 128 | # script/test 129 | - run: 130 | name: Release 131 | command: | 132 | .circleci/script/release 133 | - save_cache: 134 | paths: 135 | - ~/.m2 136 | - ~/graalvm-ce-java11-20.2.0 137 | key: mac-{{ checksum "deps.edn" }}-{{ checksum ".circleci/config.yml" }} 138 | - store_artifacts: 139 | path: /tmp/release 140 | destination: release 141 | # deploy: 142 | # docker: 143 | # - image: circleci/clojure:lein-2.8.1 144 | # working_directory: ~/repo 145 | # environment: 146 | # LEIN_ROOT: "true" 147 | # steps: 148 | # - checkout 149 | # - restore_cache: 150 | # keys: 151 | # - v1-dependencies-{{ checksum "deps.edn" }} 152 | # # fallback to using the latest cache if no exact match is found 153 | # - v1-dependencies- 154 | # - run: .circleci/script/deploy 155 | # - save_cache: 156 | # paths: 157 | # - ~/.m2 158 | # key: v1-dependencies-{{ checksum "deps.edn" }} 159 | # docker: 160 | # docker: 161 | # - image: circleci/buildpack-deps:stretch 162 | # steps: 163 | # - checkout 164 | # - setup_remote_docker: 165 | # version: 19.03.12 166 | # - run: 167 | # name: Build Docker image 168 | # command: .circleci/script/docker 169 | 170 | workflows: 171 | version: 2 172 | ci: 173 | jobs: 174 | - jvm 175 | - linux 176 | # - linux-static 177 | - mac 178 | # - deploy: 179 | # filters: 180 | # branches: 181 | # only: master 182 | # requires: 183 | # - jvm 184 | # - linux 185 | # - linux-static 186 | # - mac 187 | # - docker: 188 | # filters: 189 | # branches: 190 | # only: master 191 | # requires: 192 | # - jvm 193 | # - linux 194 | # - linux-static 195 | # - mac 196 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial code and documentation 12 | distributed under this Agreement, and 13 | b) in the case of each subsequent Contributor: 14 | i) changes to the Program, and 15 | ii) additions to the Program; 16 | 17 | where such changes and/or additions to the Program originate from and are 18 | distributed by that particular Contributor. A Contribution 'originates' from 19 | a Contributor if it was added to the Program by such Contributor itself or 20 | anyone acting on such Contributor's behalf. Contributions do not include 21 | additions to the Program which: (i) are separate modules of software 22 | distributed in conjunction with the Program under their own license 23 | agreement, and (ii) are not derivative works of the Program. 24 | 25 | "Contributor" means any person or entity that distributes the Program. 26 | 27 | "Licensed Patents" mean patent claims licensable by a Contributor which are 28 | necessarily infringed by the use or sale of its Contribution alone or when 29 | combined with the Program. 30 | 31 | "Program" means the Contributions distributed in accordance with this Agreement. 32 | 33 | "Recipient" means anyone who receives the Program under this Agreement, 34 | including all Contributors. 35 | 36 | 2. GRANT OF RIGHTS 37 | a) Subject to the terms of this Agreement, each Contributor hereby grants 38 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 39 | reproduce, prepare derivative works of, publicly display, publicly perform, 40 | distribute and sublicense the Contribution of such Contributor, if any, and 41 | such derivative works, in source code and object code form. 42 | b) Subject to the terms of this Agreement, each Contributor hereby grants 43 | Recipient a non-exclusive, worldwide, royalty-free patent license under 44 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 45 | transfer the Contribution of such Contributor, if any, in source code and 46 | object code form. This patent license shall apply to the combination of the 47 | Contribution and the Program if, at the time the Contribution is added by 48 | the Contributor, such addition of the Contribution causes such combination 49 | to be covered by the Licensed Patents. The patent license shall not apply 50 | to any other combinations which include the Contribution. No hardware per 51 | se is licensed hereunder. 52 | c) Recipient understands that although each Contributor grants the licenses to 53 | its Contributions set forth herein, no assurances are provided by any 54 | Contributor that the Program does not infringe the patent or other 55 | intellectual property rights of any other entity. Each Contributor 56 | disclaims any liability to Recipient for claims brought by any other entity 57 | based on infringement of intellectual property rights or otherwise. As a 58 | condition to exercising the rights and licenses granted hereunder, each 59 | Recipient hereby assumes sole responsibility to secure any other 60 | intellectual property rights needed, if any. For example, if a third party 61 | patent license is required to allow Recipient to distribute the Program, it 62 | is Recipient's responsibility to acquire that license before distributing 63 | the Program. 64 | d) Each Contributor represents that to its knowledge it has sufficient 65 | copyright rights in its Contribution, if any, to grant the copyright 66 | license set forth in this Agreement. 67 | 68 | 3. REQUIREMENTS 69 | 70 | A Contributor may choose to distribute the Program in object code form under its 71 | own license agreement, provided that: 72 | 73 | a) it complies with the terms and conditions of this Agreement; and 74 | b) its license agreement: 75 | i) effectively disclaims on behalf of all Contributors all warranties and 76 | conditions, express and implied, including warranties or conditions of 77 | title and non-infringement, and implied warranties or conditions of 78 | merchantability and fitness for a particular purpose; 79 | ii) effectively excludes on behalf of all Contributors all liability for 80 | damages, including direct, indirect, special, incidental and 81 | consequential damages, such as lost profits; 82 | iii) states that any provisions which differ from this Agreement are offered 83 | by that Contributor alone and not by any other party; and 84 | iv) states that source code for the Program is available from such 85 | Contributor, and informs licensees how to obtain it in a reasonable 86 | manner on or through a medium customarily used for software exchange. 87 | 88 | When the Program is made available in source code form: 89 | 90 | a) it must be made available under this Agreement; and 91 | b) a copy of this Agreement must be included with each copy of the Program. 92 | Contributors may not remove or alter any copyright notices contained within 93 | the Program. 94 | 95 | Each Contributor must identify itself as the originator of its Contribution, if 96 | any, in a manner that reasonably allows subsequent Recipients to identify the 97 | originator of the Contribution. 98 | 99 | 4. COMMERCIAL DISTRIBUTION 100 | 101 | Commercial distributors of software may accept certain responsibilities with 102 | respect to end users, business partners and the like. While this license is 103 | intended to facilitate the commercial use of the Program, the Contributor who 104 | includes the Program in a commercial product offering should do so in a manner 105 | which does not create potential liability for other Contributors. Therefore, if 106 | a Contributor includes the Program in a commercial product offering, such 107 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify 108 | every other Contributor ("Indemnified Contributor") against any losses, damages 109 | and costs (collectively "Losses") arising from claims, lawsuits and other legal 110 | actions brought by a third party against the Indemnified Contributor to the 111 | extent caused by the acts or omissions of such Commercial Contributor in 112 | connection with its distribution of the Program in a commercial product 113 | offering. The obligations in this section do not apply to any claims or Losses 114 | relating to any actual or alleged intellectual property infringement. In order 115 | to qualify, an Indemnified Contributor must: a) promptly notify the Commercial 116 | Contributor in writing of such claim, and b) allow the Commercial Contributor to 117 | control, and cooperate with the Commercial Contributor in, the defense and any 118 | related settlement negotiations. The Indemnified Contributor may participate in 119 | any such claim at its own expense. 120 | 121 | For example, a Contributor might include the Program in a commercial product 122 | offering, Product X. That Contributor is then a Commercial Contributor. If that 123 | Commercial Contributor then makes performance claims, or offers warranties 124 | related to Product X, those performance claims and warranties are such 125 | Commercial Contributor's responsibility alone. Under this section, the 126 | Commercial Contributor would have to defend claims against the other 127 | Contributors related to those performance claims and warranties, and if a court 128 | requires any other Contributor to pay any damages as a result, the Commercial 129 | Contributor must pay those damages. 130 | 131 | 5. NO WARRANTY 132 | 133 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN 134 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 135 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, 136 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each 137 | Recipient is solely responsible for determining the appropriateness of using and 138 | distributing the Program and assumes all risks associated with its exercise of 139 | rights under this Agreement , including but not limited to the risks and costs 140 | of program errors, compliance with applicable laws, damage to or loss of data, 141 | programs or equipment, and unavailability or interruption of operations. 142 | 143 | 6. DISCLAIMER OF LIABILITY 144 | 145 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 146 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 147 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 148 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 149 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 150 | OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS 151 | GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 152 | 153 | 7. GENERAL 154 | 155 | If any provision of this Agreement is invalid or unenforceable under applicable 156 | law, it shall not affect the validity or enforceability of the remainder of the 157 | terms of this Agreement, and without further action by the parties hereto, such 158 | provision shall be reformed to the minimum extent necessary to make such 159 | provision valid and enforceable. 160 | 161 | If Recipient institutes patent litigation against any entity (including a 162 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 163 | (excluding combinations of the Program with other software or hardware) 164 | infringes such Recipient's patent(s), then such Recipient's rights granted under 165 | Section 2(b) shall terminate as of the date such litigation is filed. 166 | 167 | All Recipient's rights under this Agreement shall terminate if it fails to 168 | comply with any of the material terms or conditions of this Agreement and does 169 | not cure such failure in a reasonable period of time after becoming aware of 170 | such noncompliance. If all Recipient's rights under this Agreement terminate, 171 | Recipient agrees to cease use and distribution of the Program as soon as 172 | reasonably practicable. However, Recipient's obligations under this Agreement 173 | and any licenses granted by Recipient relating to the Program shall continue and 174 | survive. 175 | 176 | Everyone is permitted to copy and distribute copies of this Agreement, but in 177 | order to avoid inconsistency the Agreement is copyrighted and may only be 178 | modified in the following manner. The Agreement Steward reserves the right to 179 | publish new versions (including revisions) of this Agreement from time to time. 180 | No one other than the Agreement Steward has the right to modify this Agreement. 181 | The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation 182 | may assign the responsibility to serve as the Agreement Steward to a suitable 183 | separate entity. Each new version of the Agreement will be given a 184 | distinguishing version number. The Program (including Contributions) may always 185 | be distributed subject to the version of the Agreement under which it was 186 | received. In addition, after a new version of the Agreement is published, 187 | Contributor may elect to distribute the Program (including its Contributions) 188 | under the new version. Except as expressly stated in Sections 2(a) and 2(b) 189 | above, Recipient receives no rights or licenses to the intellectual property of 190 | any Contributor under this Agreement, whether expressly, by implication, 191 | estoppel or otherwise. All rights in the Program not expressly granted under 192 | this Agreement are reserved. 193 | 194 | This Agreement is governed by the laws of the State of New York and the 195 | intellectual property laws of the United States of America. No party to this 196 | Agreement will bring a legal action under this Agreement more than one year 197 | after the cause of action arose. Each party waives its rights to a jury trial in 198 | any resulting litigation. 199 | -------------------------------------------------------------------------------- /src/glam/impl/impl.clj: -------------------------------------------------------------------------------- 1 | (ns glam.impl.impl 2 | {:no-doc true} 3 | (:require [clojure.edn :as edn] 4 | [clojure.java.io :as io] 5 | [clojure.java.shell :refer [sh]] 6 | [clojure.string :as str]) 7 | (:import [java.net URL HttpURLConnection] 8 | [java.nio.file Files])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (defn normalize-arch [arch] 13 | (if (= "amd64" arch) 14 | "x86_64" 15 | arch)) 16 | 17 | (def os {:os/name (System/getProperty "os.name") 18 | :os/arch (let [arch (System/getProperty "os.arch")] 19 | (normalize-arch arch))}) 20 | 21 | (defn warn [& strs] 22 | (binding [*out* *err*] 23 | (apply println strs))) 24 | 25 | (defn match-artifacts [package] 26 | (let [artifacts (:package/artifacts package)] 27 | (filter (fn [{os-name :os/name 28 | os-arch :os/arch}] 29 | (let [os-arch (normalize-arch os-arch)] 30 | (and (re-matches (re-pattern os-name) (:os/name os)) 31 | (re-matches (re-pattern os-arch) 32 | (:os/arch os))))) 33 | artifacts))) 34 | 35 | (defn unzip [{:keys [^java.io.File zip-file 36 | ^java.io.File destination-dir 37 | verbose]}] 38 | (when verbose (warn "Unzipping" (.getPath zip-file) "to" (.getPath destination-dir))) 39 | (let [output-path (.toPath destination-dir) 40 | zip-file (io/file zip-file) 41 | _ (.mkdirs (io/file destination-dir))] 42 | (with-open 43 | [fis (Files/newInputStream (.toPath zip-file) (into-array java.nio.file.OpenOption [])) 44 | zis (java.util.zip.ZipInputStream. fis)] 45 | (loop [] 46 | (let [entry (.getNextEntry zis)] 47 | (when entry 48 | (let [entry-name (.getName entry) 49 | new-path (.resolve output-path entry-name)] 50 | (if (.isDirectory entry) 51 | (Files/createDirectories new-path (into-array [])) 52 | (Files/copy ^java.io.InputStream zis 53 | new-path 54 | ^"[Ljava.nio.file.CopyOption;" 55 | (into-array 56 | [java.nio.file.StandardCopyOption/REPLACE_EXISTING])))) 57 | (recur))))))) 58 | 59 | (defn un-tgz [^java.io.File zip-file ^java.io.File destination-dir verbose?] 60 | (when verbose? (warn "Unzipping" (.getPath zip-file) "to" (.getPath destination-dir))) 61 | (let [tmp-file (java.io.File/createTempFile "glam" ".tar") 62 | output-path (.toPath tmp-file)] 63 | (with-open 64 | [fis (Files/newInputStream (.toPath zip-file) (into-array java.nio.file.OpenOption [])) 65 | zis (java.util.zip.GZIPInputStream. fis)] 66 | (Files/copy ^java.io.InputStream zis 67 | output-path 68 | ^"[Ljava.nio.file.CopyOption;" 69 | (into-array 70 | [java.nio.file.StandardCopyOption/REPLACE_EXISTING]))) 71 | (sh "tar" "xf" (.getPath tmp-file) "--directory" (.getPath destination-dir)) 72 | (.delete tmp-file))) 73 | 74 | (defn make-executable [dest-dir executables verbose?] 75 | (doseq [e executables] 76 | (let [f (io/file dest-dir e)] 77 | (when verbose? (warn "Making" (.getPath f) "executable.")) 78 | (.setExecutable f true)))) 79 | 80 | (defn download [source ^java.io.File dest verbose?] 81 | (when verbose? (warn "Downloading" source "to" (.getPath dest))) 82 | (let [source (URL. source) 83 | dest (io/file dest) 84 | conn ^HttpURLConnection (.openConnection ^URL source)] 85 | (.setInstanceFollowRedirects conn true) 86 | (.connect conn) 87 | (io/make-parents dest) 88 | (with-open [is (.getInputStream conn)] 89 | (io/copy is dest)) 90 | (when verbose? (warn "Download complete.")))) 91 | 92 | (def glam-dir 93 | (delay (io/file (System/getProperty "user.home") 94 | ".glam"))) 95 | 96 | (def cfg-dir 97 | (delay (let [config-dir (or (System/getenv "XDG_CONFIG_HOME") 98 | (io/file (System/getProperty "user.home") 99 | ".config"))] 100 | (io/file config-dir "glam")))) 101 | 102 | (def cfg-file 103 | (delay (io/file @cfg-dir "glam.edn"))) 104 | 105 | (defn repo-config [] 106 | (:glam/repos (edn/read-string (slurp @cfg-file)))) 107 | 108 | (defn repo-dir [repo-name] 109 | (io/file @glam-dir "packages" (str repo-name))) 110 | 111 | (defn repo-dirs [repo-cfg] 112 | (let [names (map :repo/name repo-cfg)] 113 | (map repo-dir names))) 114 | 115 | (defn package-resource 116 | ([package-name] 117 | (package-resource package-name (repo-config))) 118 | ([package-name repo-cfg] 119 | (let [dirs (repo-dirs repo-cfg) 120 | f (str package-name)] 121 | (some (fn [dir] 122 | (let [f (io/file dir f)] 123 | (when (.exists f) 124 | f))) 125 | dirs)))) 126 | 127 | (defn find-package-descriptor [package] 128 | (if (not (map? package)) 129 | (let [resource (str package ".glam.edn")] 130 | (if-let [f (package-resource resource)] 131 | (let [pkg (edn/read-string (slurp f))] 132 | pkg) 133 | ;; Template fallback 134 | (let [[package version] (str/split package #"@")] 135 | (if version 136 | (let [template-resource (str package ".glam.template.edn")] 137 | (if-let [f (package-resource template-resource)] 138 | (let [pkg-str (slurp f) 139 | pkg-str (str/replace pkg-str "{{version}}" version) 140 | pkg (edn/read-string pkg-str)] 141 | (warn "Package" package "not found, attempting template fallback") 142 | pkg) 143 | (warn "Package" package "not found"))) 144 | (warn "Package" package "not found"))))) 145 | package)) 146 | 147 | (defn pkg-name [package] 148 | (str (:package/name package) "@" 149 | (:package/version package))) 150 | 151 | (defn cache-dir 152 | ^java.io.File 153 | [{package-name :package/name 154 | package-version :package/version}] 155 | (io/file (or 156 | (System/getenv "XDG_CACHE_HOME") 157 | (System/getProperty "user.home")) 158 | ".glam" 159 | "repository" 160 | (str package-name) 161 | package-version)) 162 | 163 | (defn data-dir 164 | ^java.io.File 165 | [{package-name :package/name 166 | package-version :package/version}] 167 | (io/file (or 168 | (System/getenv "XDG_DATA_HOME") 169 | (System/getProperty "user.home")) 170 | ".glam" 171 | "repository" 172 | (str package-name) 173 | package-version)) 174 | 175 | (defn sha256 [file] 176 | (let [buf (byte-array 8192) 177 | digest (java.security.MessageDigest/getInstance "SHA-256")] 178 | (with-open [bis (io/input-stream (io/file file))] 179 | (loop [] 180 | (let [count (.read bis buf)] 181 | (when (pos? count) 182 | (.update digest buf 0 count) 183 | (recur))))) 184 | (-> (.encode (java.util.Base64/getEncoder) 185 | (.digest digest)) 186 | (String. "UTF-8")))) 187 | 188 | (defn install-package [package force? verbose? _global?] 189 | (when-let [package (find-package-descriptor package)] 190 | (let [artifacts (match-artifacts package) 191 | cdir (cache-dir package) 192 | ddir (data-dir package)] 193 | (mapv (fn [artifact] 194 | (let [url (:artifact/url artifact) 195 | file-name (last (str/split url #"/")) 196 | cache-file (io/file cdir file-name)] 197 | (if (and (not force?) (.exists cdir)) 198 | (when verbose? 199 | (warn "Package" (pkg-name package) "already installed")) 200 | (do (download url cache-file verbose?) 201 | (when-let [expected-sha (:artifact/hash artifact)] 202 | (let [sha (sha256 cache-file)] 203 | (when-not (= (str/replace expected-sha #"^sha256:" "") 204 | sha) 205 | (throw (ex-info (str "Wrong SHA-256 for file" (str cache-file)) 206 | {:sha sha 207 | :expected-sha expected-sha}))))) 208 | (let [filename (.getName cache-file)] 209 | (cond (str/ends-with? filename ".zip") 210 | (unzip {:zip-file cache-file 211 | :destination-dir ddir 212 | :verbose verbose?}) 213 | (or (str/ends-with? filename ".tgz") 214 | (str/ends-with? filename ".tar.gz")) 215 | (un-tgz cache-file ddir 216 | verbose?))) 217 | (make-executable ddir (:artifact/executables artifact) verbose?))) 218 | (.getPath ddir))) artifacts) 219 | ddir))) 220 | 221 | (def path-sep (System/getProperty "path.separator")) 222 | 223 | (defn dep-with-version-suffix [dep-name m] 224 | (let [v (:dep/version m)] 225 | (str dep-name 226 | (when (and v (not (identical? :latest v))) 227 | (str "@" v))))) 228 | 229 | (defn project-packages [] 230 | (let [glam-edn (io/file "glam.edn")] 231 | (when (.exists glam-edn) 232 | (let [edn (edn/read-string (slurp glam-edn)) 233 | deps (:glam/deps edn) 234 | deps (mapv (fn [[k m]] 235 | (dep-with-version-suffix k m)) 236 | deps)] 237 | deps)))) 238 | 239 | (defn global-packages [] 240 | (let [glam-edn (io/file @cfg-dir "glam.edn")] 241 | (when (.exists glam-edn) 242 | (let [edn (edn/read-string (slurp glam-edn)) 243 | deps (:glam/deps edn) 244 | deps (mapv (fn [[k m]] 245 | (dep-with-version-suffix k m)) 246 | deps)] 247 | deps)))) 248 | 249 | (defn install [packages force? verbose? global?] 250 | (let [global-pkgs (global-packages) 251 | installed-glb (keep find-package-descriptor global-pkgs) 252 | installed-glb (keep #(install-package % force? verbose? global?) installed-glb) 253 | global-path (str/join path-sep installed-glb) 254 | project-pkgs (project-packages) 255 | installed-proj (keep find-package-descriptor project-pkgs) 256 | installed-proj (keep #(install-package % force? verbose? global?) installed-proj) 257 | proj-path (str/join path-sep installed-proj) 258 | pkgs (keep find-package-descriptor packages) 259 | pkgs (keep #(install-package % force? verbose? global?) pkgs) 260 | path (str/join path-sep pkgs) 261 | global-path-file (io/file @glam-dir "path")] 262 | (spit global-path-file global-path) 263 | (when verbose? 264 | (warn "Wrote" (.getPath global-path-file))) 265 | (when (.exists (io/file "glam.edn")) 266 | (let [path-file (io/file ".glam" "path")] 267 | (io/make-parents path-file) 268 | (spit path-file proj-path) 269 | (when verbose? 270 | (warn "Wrote" (.getPath path-file))))) 271 | {:path path 272 | :exit (if (= (+ (count global-pkgs) (count project-pkgs) (count packages)) 273 | (+ (count installed-glb) (count installed-proj) (count pkgs))) 274 | 0 275 | 1)})) 276 | 277 | (defn pull-packages [] 278 | (let [cfg (repo-config)] 279 | (doseq [{repo-name :repo/name 280 | git-url :git/url} cfg] 281 | (let [repo-dir (io/file (io/file (System/getProperty "user.home") 282 | ".glam" "packages") 283 | (str repo-name)) 284 | exists? (.exists (io/file repo-dir ".git"))] 285 | (if exists? 286 | (do 287 | (warn "Pulling" git-url "to" (str repo-dir)) 288 | ;; TODO: error handling 289 | (sh "git" "-C" (.getPath repo-dir) "pull" git-url)) 290 | (do 291 | (.mkdirs repo-dir) 292 | (warn "Cloning" git-url "to" (str repo-dir)) 293 | (sh "git" "-C" (.getParent repo-dir) "clone" git-url 294 | (last (str/split (str repo-dir) #"/"))))))))) 295 | 296 | (def windows? 297 | (str/starts-with? (:os/name os) "Win")) 298 | 299 | (defn setup [force?] 300 | (let [scripts ["glam.sh" "glam.cmd"] 301 | script-dir (io/file @glam-dir "scripts")] 302 | (.mkdirs script-dir) 303 | (doseq [s scripts] 304 | (let [script-file (io/file script-dir s)] 305 | (spit script-file (slurp (io/resource (str "glam/scripts/" s)))))) 306 | (let [^java.io.File cfg-file @cfg-file] 307 | (when (or (not (.exists cfg-file)) 308 | force?) 309 | (io/make-parents cfg-file) 310 | (spit cfg-file (slurp (io/resource "glam/glam.edn"))))) 311 | (pull-packages) 312 | (warn) 313 | (if windows? 314 | (warn "Add" (str script-dir) "to %PATH% to finish setup.") 315 | (do (warn "Include this in your .bashrc analog to finish setup:") 316 | (warn) 317 | (warn "source" "$HOME/.glam/scripts/glam.sh"))))) 318 | 319 | ;;;; Package creation 320 | 321 | (defn artifact-sha [artifact] 322 | (when-let [k (:artifact/hash artifact)] 323 | (let [url (:artifact/url artifact) 324 | tmp-file (java.io.File/createTempFile "glam" "glam") 325 | _ (download url tmp-file true) 326 | sha (sha256 tmp-file) 327 | _ (.delete tmp-file)] 328 | [k sha]))) 329 | 330 | (defn calculate-hashes [pkg] 331 | (map artifact-sha (:package/artifacts pkg))) 332 | 333 | (defn package-add [[package]] 334 | (let [[package version] (str/split package #"@")] 335 | (if version 336 | (let [template-resource (str package ".glam.template.edn") 337 | f (io/file template-resource)] 338 | (if (.exists f) 339 | (let [pkg-dir (-> f .getParentFile .getParentFile) 340 | pkg-str (slurp f) 341 | pkg-str (str/replace pkg-str "{{version}}" version) 342 | pkg-edn (edn/read-string pkg-str) 343 | replacements (calculate-hashes pkg-edn) 344 | pkg-str (reduce (fn [acc [k v]] 345 | (str/replace acc k (str "sha256:" v))) 346 | pkg-str 347 | replacements) 348 | pkg-file (io/file pkg-dir (str package "@" version ".glam.edn"))] 349 | (spit pkg-file pkg-str) 350 | (warn "Package created at" (str pkg-file))) 351 | (warn "No template found"))) 352 | (warn "Please specify version using @version")))) 353 | 354 | (defn package-set-current [[package-with-version]] 355 | (let [[package version] (str/split package-with-version #"@")] 356 | (if version 357 | (let [resource (str package-with-version ".glam.edn") 358 | f (io/file resource)] 359 | (if (.exists f) 360 | (let [pkg-dir (-> f .getParentFile .getParentFile) 361 | pkg-str (slurp f) 362 | pkg-file (io/file pkg-dir (str package ".glam.edn"))] 363 | (spit pkg-file pkg-str) 364 | (warn "Package created at" (str pkg-file))) 365 | (warn "Package not found:" package-with-version))) 366 | (warn "Please specify version using @version")))) 367 | --------------------------------------------------------------------------------