Skip to content

Commit 378e7e7

Browse files
authored
Windows Support (#22)
* - add `windows?` constant for checking if running on windows - make bbin-root using OS-dependent path separator * - pull out script installation IO to a separate function - don't try to chmod on windows * use OS-dependent file separator * add docstring * add extension when on windows * swap `File/separator` for `fs` version * different commands for running scripts in bash or windows * change cmd script extension to bat (windows) * add bbin bat file for windows * script-name-fn no longer needed * - when on windows, write a bb script that execs the 'proper' bb invocation, and write a batch file that calls it * - remove leading spaces that break ls - delete wrapper batch file when on windows - always look for semicolon on windows from parse-script * add some basic flows to design doc (including capturing Windows differences) * - add tool template for windows - add shebangs to windows scripts (to later test if we can use the bb scripts for all cases) * add extra line to tool usage output to match bash script * add mock local tool * add local-rooted tool test * add Windows install instructions * commit updated script * add note about line endings for Windows to readme * a bunch of little cleanup things * correct tag in readme * wipe the windows section from the readme (until it gets updated with new version)
1 parent 2ab0cf9 commit 378e7e7

File tree

10 files changed

+346
-63
lines changed

10 files changed

+346
-63
lines changed

README.template.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,21 @@ echo 'export PATH="$PATH:$HOME/.bbin/bin"' >> ~/.zshrc && exec /bin/zsh
7070
echo 'export PATH="$PATH:$HOME/.bbin/bin"' >> ~/.bashrc && exec /bin/bash
7171
```
7272

73+
### Windows (Manual)
74+
75+
**1. Install `bbin` CLI (and batch file, because Windows):**
76+
```shell
77+
> set BBIN_DEST=%HOMEDRIVE%%HOMEPATH%\.bbin\bin
78+
> mkdir %BBIN_DEST% && curl -o %BBIN_DEST%\bbin -L https://raw.githubusercontent.com/babashka/bbin/v{{version}}/bbin && curl -o %BBIN_DEST%\bbin.bat -L https://raw.githubusercontent.com/babashka/bbin/v{{version}}/bbin.bat
79+
> set BBIN_DEST=
80+
```
81+
82+
**2. Add `%HOMEDRIVE%%HOMEPATH%\.bbin\bin` to `Path` environment variable**
83+
84+
Because `Path` is often a system-level and user-level variable, setting from the command line can be messy; it's probably easiest to add it in the Environment Variables settings window.
85+
86+
Also, remember to check line endings.
87+
7388
## Usage
7489

7590
```

bbin

Lines changed: 139 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Usage: bbin <command>
5050
(defn now []
5151
(Date.))
5252

53-
(def ^:dynamic *bbin-root* (fs/expand-home "~/.bbin"))
53+
(def ^:dynamic *bbin-root* (fs/expand-home (str "~" fs/file-separator ".bbin")))
5454

5555
(defn bbin-root [_]
5656
*bbin-root*)
@@ -66,6 +66,11 @@ Usage: bbin <command>
6666
(defn ensure-bbin-dirs [cli-opts]
6767
(fs/create-dirs (bin-dir cli-opts)))
6868

69+
(def windows?
70+
(some-> (System/getProperty "os.name")
71+
(str/lower-case)
72+
(str/index-of "win")))
73+
6974
(defn print-version [& {:as opts}]
7075
(if (:help opts)
7176
(print-help)
@@ -105,16 +110,69 @@ Usage: bbin <command>
105110
[clojure.pprint :as pprint]
106111
[selmer.parser :as selmer]
107112
[selmer.util :as selmer-util]
113+
[selmer.filters :as filters]
108114
[babashka.bbin.util :as util :refer [sh]]))
109115

110116
(defn- pprint [x _]
111117
(pprint/pprint x))
112118

113119
(defn- gitlib-path [cli-opts script-deps]
114120
(let [coords (val (first script-deps))]
115-
(fs/expand-home (str "~/.gitlibs/libs/" (:script/lib cli-opts) "/" (:git/sha coords)))))
121+
(fs/expand-home (str/join fs/file-separator ["~" ".gitlibs" "libs" (:script/lib cli-opts) (:git/sha coords)]))))
122+
123+
; windows scripts are babashka/clojure instead of shell scripts, so we need a variable
124+
; comment character in the script meta
125+
(def ^:private comment-char
126+
(if util/windows? "; " "# "))
127+
128+
(def windows-wrapper-extension ".bat")
129+
130+
; selmer filter for clojure escaping for e.g. files
131+
(filters/add-filter! :pr-str (comp pr-str str))
116132

117133
(def ^:private tool-template-str
134+
(if util/windows?
135+
"#!/usr/bin/env bb
136+
; :bbin/start
137+
;
138+
{{script/meta}}
139+
; :bbin/end
140+
(require '[babashka.process :as process]
141+
'[babashka.fs :as fs]
142+
'[clojure.string :as str])
143+
144+
(let [SCRIPT_ROOT {{script/root|pr-str}}
145+
SCRIPT_LIB '{{script/lib}}
146+
SCRIPT_COORDS {{script/coords|str}}
147+
SCRIPT_NS_DEFAULT '{{script/ns-default}}
148+
SCRIPT_NAME (fs/file-name *file*)
149+
TMP_EDN (doto (fs/file (fs/temp-dir) (str (gensym \"bbin\")))
150+
(spit (str \"{:deps {\" SCRIPT_LIB SCRIPT_COORDS \"}}\"))
151+
(fs/delete-on-exit))
152+
[first-arg & rest-args] *command-line-args*]
153+
(if first-arg
154+
(process/exec
155+
(str/join \" \" (concat [\"bb --deps-root \" SCRIPT_ROOT \"--config\" (str TMP_EDN)
156+
\"-x\" (str SCRIPT_NS_DEFAULT \"/\" first-arg)
157+
\"--\"] rest-args)))
158+
(do
159+
(let [script (str \"(require '\" SCRIPT_NS_DEFAULT \")
160+
(def fns (filter #(fn? (deref (val %))) (ns-publics '\" SCRIPT_NS_DEFAULT \")))
161+
(def max-width (->> (keys fns) (map (comp count str)) (apply max)))
162+
(defn pad-right [x] (format (str \\\"%-\\\" max-width \\\"s\\\") x))
163+
(println (str \\\"Usage: \" SCRIPT_NAME \" <command>\\\"))
164+
(newline)
165+
(doseq [[k v] fns]
166+
(println
167+
(str \\\" \" SCRIPT_NAME \" \\\" (pad-right k) \\\" \\\"
168+
(when (:doc (meta v))
169+
(first (str/split-lines (:doc (meta v))))))))\")
170+
cmd-line [\"bb\" \"--deps-root\" SCRIPT_ROOT \"--config\" (str TMP_EDN)
171+
\"-e\" script]]
172+
(process/exec cmd-line)))))"
173+
;
174+
; non-windows tool script
175+
;
118176
"#!/usr/bin/env bash
119177
set -e
120178
@@ -152,9 +210,36 @@ else
152210
--config <(echo \"{:deps {$SCRIPT_LIB $SCRIPT_COORDS}}\") \\
153211
-x $SCRIPT_NS_DEFAULT/$1 \\
154212
-- \"${@:2}\"
155-
fi")
213+
fi"))
156214

157215
(def ^:private git-or-local-template-str
216+
(if util/windows?
217+
"#!/usr/bin/env bb
218+
; :bbin/start
219+
;
220+
{{script/meta}}
221+
; :bbin/end
222+
223+
(require '[babashka.process :as process]
224+
'[babashka.fs :as fs]
225+
'[clojure.string :as str])
226+
227+
(let [SCRIPT_ROOT {{script/root|pr-str}}
228+
SCRIPT_LIB '{{script/lib}}
229+
SCRIPT_COORDS {{script/coords|str}}
230+
SCRIPT_MAIN_OPTS_FIRST {{script/main-opts.0|pr-str}}
231+
SCRIPT_MAIN_OPTS_SECOND {{script/main-opts.1|pr-str}}
232+
TMP_EDN (doto (fs/file (fs/temp-dir) (str (gensym \"bbin\")))
233+
(spit (str \"{:deps {\" SCRIPT_LIB SCRIPT_COORDS\"}}\"))
234+
(fs/delete-on-exit))]
235+
236+
(process/exec
237+
(str/join \" \" (concat [\"bb --deps-root\" SCRIPT_ROOT \"--config\" (str TMP_EDN)
238+
SCRIPT_MAIN_OPTS_FIRST SCRIPT_MAIN_OPTS_SECOND
239+
\"--\"] *command-line-args*))))"
240+
;
241+
; non-windows script template
242+
;
158243
"#!/usr/bin/env bash
159244
set -e
160245
@@ -174,7 +259,7 @@ exec bb \\
174259
--deps-root \"$SCRIPT_ROOT\" \\
175260
--config <(echo \"{:deps {$SCRIPT_LIB $SCRIPT_COORDS}}\") \\
176261
$SCRIPT_MAIN_OPTS_FIRST \"$SCRIPT_MAIN_OPTS_SECOND\" \\
177-
-- \"$@\"")
262+
-- \"$@\""))
178263

179264
(defn- http-url->script-name [http-url]
180265
(first
@@ -201,6 +286,23 @@ exec bb \\
201286
code)]
202287
(str/join "\n" next-lines)))
203288

289+
(defn- install-script
290+
"Spits `contents` to `path` (adding an extension on Windows), or
291+
pprints them if `dry-run?` is truthy.
292+
Side-effecting."
293+
[path contents dry-run?]
294+
(let [path-str (str path)]
295+
(if dry-run?
296+
(pprint {:script-file path-str
297+
:script-contents contents}
298+
dry-run?)
299+
(do
300+
(spit path-str contents)
301+
(when-not util/windows? (sh ["chmod" "+x" path-str]))
302+
(when util/windows?
303+
(spit (str path-str windows-wrapper-extension) (str "@bb -f %~dp0" (fs/file-name path-str) " -- %*")))
304+
nil))))
305+
204306
(defn- install-http [cli-opts]
205307
(let [http-url (:script/lib cli-opts)
206308
script-deps {:bbin/url http-url}
@@ -211,14 +313,7 @@ exec bb \\
211313
(insert-script-header header))
212314
script-file (fs/canonicalize (fs/file (util/bin-dir cli-opts) script-name)
213315
{:nofollow-links true})]
214-
(if (:dry-run cli-opts)
215-
(pprint {:script-file (str script-file)
216-
:script-contents script-contents}
217-
cli-opts)
218-
(do
219-
(spit (str script-file) script-contents)
220-
(sh ["chmod" "+x" (str script-file)])
221-
nil))))
316+
(install-script script-file script-contents (:dry-run cli-opts))))
222317

223318
(defn- default-script-config [cli-opts]
224319
(let [[ns name] (str/split (:script/lib cli-opts) #"/")
@@ -253,7 +348,7 @@ exec bb \\
253348
(:main-opts script-config))
254349
template-opts {:script/meta (->> script-edn-out
255350
str/split-lines
256-
(map #(str "# " %))
351+
(map #(str comment-char %))
257352
(str/join "\n"))
258353
:script/root script-root
259354
:script/lib (pr-str (key (first script-deps)))
@@ -272,16 +367,34 @@ exec bb \\
272367
template-out (selmer-util/without-escaping
273368
(selmer/render template-str template-opts'))
274369
script-file (fs/canonicalize (fs/file (util/bin-dir cli-opts) script-name) {:nofollow-links true})]
275-
(if (:dry-run cli-opts)
276-
(pprint {:script-file (str script-file)
277-
:template-out template-out}
278-
cli-opts)
279-
(do
280-
(spit (str script-file) template-out)
281-
(sh ["chmod" "+x" (str script-file)])
282-
nil))))
370+
(install-script script-file template-out (:dry-run cli-opts))))
283371

284372
(def ^:private maven-template-str
373+
(if util/windows?
374+
"#!/usr/bin/env bb
375+
; :bbin/start
376+
;
377+
{{script/meta}}
378+
; :bbin/end
379+
(require '[babashka.process :as process]
380+
'[babashka.fs :as fs]
381+
'[clojure.string :as str])
382+
383+
(let [SCRIPT_LIB '{{script/lib}}
384+
SCRIPT_COORDS {{script/coords|str}}
385+
SCRIPT_MAIN_OPTS_FIRST {{script/main-opts.0|pr-str}}
386+
SCRIPT_MAIN_OPTS_SECOND {{script/main-opts.1|pr-str}}
387+
TMP_EDN (doto (fs/file (fs/temp-dir) (str (gensym \"bbin\")))
388+
(spit (str \"{:deps {\" SCRIPT_LIB SCRIPT_COORDS \"}}\"))
389+
(fs/delete-on-exit))]
390+
391+
(process/exec
392+
(str/join \" \" (concat [\"bb --config\" (str TMP_EDN)
393+
SCRIPT_MAIN_OPTS_FIRST SCRIPT_MAIN_OPTS_SECOND
394+
\"--\"] *command-line-args*))))\""
395+
;
396+
; non-windows script template
397+
;
285398
"#!/usr/bin/env bash
286399
set -e
287400
@@ -299,7 +412,7 @@ SCRIPT_MAIN_OPTS_SECOND='{{script/main-opts.1}}'
299412
exec bb \\
300413
--config <(echo \"{:deps {$SCRIPT_LIB $SCRIPT_COORDS}}\") \\
301414
$SCRIPT_MAIN_OPTS_FIRST \"$SCRIPT_MAIN_OPTS_SECOND\" \\
302-
-- \"$@\"")
415+
-- \"$@\""))
303416

304417
(defn- install-deps-maven [cli-opts]
305418
(let [script-deps {(edn/read-string (:script/lib cli-opts))
@@ -318,7 +431,7 @@ exec bb \\
318431
(:main-opts script-config))
319432
template-opts {:script/meta (->> script-edn-out
320433
str/split-lines
321-
(map #(str "# " %))
434+
(map #(str comment-char %))
322435
(str/join "\n"))
323436
:script/root script-root
324437
:script/lib (pr-str (key (first script-deps)))
@@ -331,14 +444,7 @@ exec bb \\
331444
template-out (selmer-util/without-escaping
332445
(selmer/render maven-template-str template-opts))
333446
script-file (fs/canonicalize (fs/file (util/bin-dir cli-opts) script-name) {:nofollow-links true})]
334-
(if (:dry-run cli-opts)
335-
(pprint {:script-file (str script-file)
336-
:template-out template-out}
337-
cli-opts)
338-
(do
339-
(spit (str script-file) template-out)
340-
(sh ["chmod" "+x" (str script-file)])
341-
nil))))
447+
(install-script script-file template-out (:dry-run cli-opts))))
342448

343449
(defn- parse-script [s]
344450
(let [lines (str/split-lines s)
@@ -356,6 +462,7 @@ exec bb \\
356462
(filter #(.isFile %))
357463
(map (fn [x] [(symbol (str (fs/relativize (util/bin-dir cli-opts) x)))
358464
(parse-script (slurp x))]))
465+
(filter second)
359466
(into {})))
360467

361468
(defn ls [cli-opts]
@@ -386,6 +493,7 @@ exec bb \\
386493
(let [script-name (:script/lib cli-opts)
387494
script-file (fs/canonicalize (fs/file (util/bin-dir cli-opts) script-name) {:nofollow-links true})]
388495
(when (fs/delete-if-exists script-file)
496+
(when util/windows? (fs/delete-if-exists (fs/file (str script-file windows-wrapper-extension))))
389497
(println "Removing" (str script-file)))))))
390498

391499
(ns babashka.bbin.cli

bbin.bat

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@echo off
2+
set ARGS=%*
3+
set SCRIPT=%~dp0bbin
4+
bb -f %SCRIPT% %ARGS%

docs/design.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,21 @@
55
- Provide a CLI that's easy to remember and type
66
- Allow project authors to provide default script names
77
- When possible, follow existing patterns from `tools.deps` for managing dependencies
8+
9+
10+
## Basic Flows
11+
### `install`
12+
- Determine the type of install (http, maven, etc)
13+
- Build the script from the corresponding template
14+
- Selmer template rendering of a bash script (or a babashka script on Windows)
15+
- Script also includes commented metadata for use by `ls`
16+
- Write script out to bbin's bin directory (~/.bbin/bin)
17+
- On Windows, a batch file is also written to provide a command-line executable
18+
19+
### `uninstall`
20+
- Delete the script created by install
21+
- On Windows, also delete the batch file
22+
23+
### `ls`
24+
- Find all files in the bin directory that contain metadata
25+
- pprint a map of script -> metadata

0 commit comments

Comments
 (0)