diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..d05f8592 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,60 @@ +name: Test + +on: + push: + branches: ['master'] + pull_request: + +jobs: + build: + runs-on: ${{ matrix.os }}-latest + strategy: + fail-fast: false + matrix: + os: [ ubuntu, macos, windows ] + + name: ${{ matrix.os }} + + steps: + + - uses: actions/checkout@v3 + + - name: Clojure deps cache + uses: actions/cache@v3 + with: + path: | + ~/.m2/repository + key: ${{ runner.os }}-cljdeps-${{ hashFiles('project.clj') }} + restore-keys: ${{ runner.os }}-cljdeps- + + - name: "Setup Java" + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '11' + + - name: Install Clojure Tools + uses: DeLaGuardo/setup-clojure@5.1 + with: + bb: 'latest' + lein: 'latest' + + - name: Tools versions + run: bb tools-versions + + - name: Bring down deps + run: lein deps + + - name: Launch Virtual Display (ubuntu) + if: matrix.os == 'ubuntu' + run: Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & + + - name: Run Tests (unbuntu) + if: matrix.os == 'ubuntu' + run: lein test + env: + DISPLAY: :99.0 + + - name: Run Tests (macos, windows) + if: matrix.os != 'ubuntu' + run: lein test diff --git a/bb.edn b/bb.edn new file mode 100644 index 00000000..f29650f9 --- /dev/null +++ b/bb.edn @@ -0,0 +1,3 @@ +{:paths ["script"] + :deps {doric/doric {:mvn/version "0.9.0"}} + :tasks {tools-versions {:task tools-versions/report :doc "report on tools versions"}}} diff --git a/script/tools_versions.clj b/script/tools_versions.clj new file mode 100644 index 00000000..d35ca017 --- /dev/null +++ b/script/tools_versions.clj @@ -0,0 +1,146 @@ +(ns tools-versions + (:require [babashka.fs :as fs] + [babashka.tasks :as tasks] + [clojure.string :as string] + [doric.core :as doric] + [cheshire.core :as json])) + +(def tools + [;; earlier versions of java used -version and spit version info to stderr + {:oses :all :name "Java" :type :bin :app "java" :args "-version" :shell-opts {:out :string :err :string :continue true}} + {:oses :all :name "Leiningen" :type :bin :app "lein"} + {:oses :all :name "Babashka" :type :bin :app "bb"} + + {:oses [:unix] :name "Chrome" :type :bin :app "google-chrome"} ;; only handling nix for now + {:oses [:mac] :name "Chrome" :type :mac-app :app "Google Chrome"} + {:oses [:win] :name "Chrome" :type :win-package :app "Google Chrome"} + {:oses :all :name "Chrome Webdriver" :type :bin :app "chromedriver"} + + {:oses [:unix] :name "Firefox" :type :bin :app "firefox"} ;; only handling nix for now + {:oses [:mac] :name "Firefox" :type :mac-app :app "Firefox"} + {:oses [:win] :name "Firefox" :type :win-package :app #"Mozilla Firefox .*"} + {:oses :all :name "Firefox Webdriver" :type :bin :app "geckodriver" :version-post-fn #(->> % string/split-lines first)} + + {:oses [:mac] :name "Edge" :type :mac-app :app "Microsoft Edge"} + {:oses [:win] :name "Edge" :type :win-package :app "Microsoft Edge"} + {:oses [:win :mac] :name "Edge Webdriver" :type :bin :app "msedgedriver"} + + {:oses [:mac] :name "Safari" :type :mac-app :app "Safari"} + {:oses [:mac] :name "Safari Webdriver" :type :bin :app "safaridriver"}]) + +(def tool-defaults {:shell-opts {:out :string :continue true} + :args "--version" + :version-post-fn identity}) + +(defn- get-os [] + (let [os-name (string/lower-case (System/getProperty "os.name"))] + (condp re-find os-name + #"win" :win + #"mac" :mac + #"(nix|nux|aix)" :unix + :unknown))) + +(defn- expected-on-this-os [{:keys [oses]}] + (or (= :all oses) + (some #{(get-os)} oses))) + +(defn- version-cmd-result [shell-opts {:keys [out err exit]}] + (if (not (zero? exit)) + (format "" exit) + (cond-> "" + (= :string (:out shell-opts)) (str out) + (= :string (:err shell-opts)) (str err)))) + +(defn- table-multilines->rows + "Convert a seq of maps from [{:a \"one\n\two\" :b \"a\nb\nc\"}] + to: [{:a \"one\" :b \"a\"} + {:a \"two\" :b \"b\"} + {:a \"\" :b \"c\"}] + in preparation for printing with doric." + [results] + (reduce (fn [acc n] + (let [n (reduce-kv (fn [m k v] + (assoc m k (string/split-lines v))) + {} + n) + max-lines (apply max (map #(count (val %)) n))] + (concat acc + (for [ln (range max-lines)] + (reduce-kv (fn [m k _v] + (assoc m k (get (k n) ln ""))) + {} + n))))) + [] + results)) + +(defn- windows-software-list* + "One way to get a list of installed software on Windows. + Seems like there are many many ways, but this also often gets the install + location which is interesting to report." + [] + (let [reg-keys ["\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*" + "\\Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*"]] + (->> (mapcat (fn [reg-key] + (-> (tasks/shell {:out :string :continue true} + "powershell" + "-command" + (format "Get-ItemProperty HKLM:%s | Select-Object DisplayName, DisplayVersion, InstallLocation | ConvertTo-Json" + reg-key)) + :out + json/parse-string)) + reg-keys)))) + +(def windows-software-list (memoize windows-software-list*)) + +(defmulti resolve-tool :type) + +(defmethod resolve-tool :win-package + [{:keys [app]}] + (if-let [found-package (->> (windows-software-list) + (filter (fn [p] + (when-let [pname (get p "DisplayName")] + (if (string? app) + (= app pname) + (re-matches app pname))))) + first)] + {:app (get found-package "InstallLocation" "?") + :version (get found-package "DisplayVersion" "?")} + {:error (format "" app)})) + +(defmethod resolve-tool :mac-app + [{:keys [app shell-opts version-post-fn]}] + (let [app-dir (format "/Applications/%s.app" app)] + (if (fs/exists? app-dir) + {:app app-dir + :version + (->> (tasks/shell shell-opts (format "defaults read '%s/Contents/Info' CFBundleShortVersionString" app-dir)) + (version-cmd-result shell-opts) + version-post-fn)} + {:error (format "" app)}))) + +(defmethod resolve-tool :bin + [{:keys [app shell-opts args version-post-fn]}] + (if-let [found-bin (some-> (fs/which app {:win-exts ["com" "exe" "bat" "cmd" "ps1"]}) + str)] + {:app found-bin + :version (->> (if (string/ends-with? found-bin ".ps1") + (tasks/shell shell-opts "powershell" "-command" found-bin args) + (tasks/shell shell-opts found-bin args)) + (version-cmd-result shell-opts) + version-post-fn)} + {:error (format "" app)})) + +(defn report + "Report on tools versions based the the OS the script it is run from. + Currently informational only, should always return 0 unless, of course, + something exceptional happens." + [] + (->> (for [{:keys [name] :as t} (map #(merge tool-defaults %) tools) + :when (expected-on-this-os t) + :let [{:keys [error app version]} (resolve-tool t)]] + (if error + {:name name :version error} + {:name name :app app :version version})) + table-multilines->rows + (doric/table [:name :version :app]) + println))