Main opts with space broken apart

Description

src/foo/core.clj

Expectation is that there be two options passed to foo.core/-main, not three.

Environment

macOS
clj version 1.9.0.358

Attachments

4

Activity

Show:

Alex Miller February 17, 2021 at 10:40 PM

I have implemented a fix for this in Clojure prerelease 1.10.2.786. I changed the format of the file written by the Clojure code to be arg per line (not single space-separated line), and then used bash read to read that file into an array with IFS set to \n - this does the "right" thing and will not word-split on spaces. In Windows, did similar approach with Get-Content reading into array.

Note, the IFS/read solution is available in bash 3.2 which has been the default install for Macs since forever. bash 4.0+ is GPL and not installed by default on Macs (but you can upgrade with brew etc). In bash 4, I believe the preferred solution would be to use mapfile, but I've explicitly NOT chosen to use this due to the Mac version issues, noting here for the historical record.

New Clojure versions automatically invalidate all existing caches (due to staleness check on the installed root deps.edn) so there should be no issue with having new invocations see old cached files in different format. I tested this on both Mac and Windows.

dottedmag@dottedmag.net December 6, 2018 at 10:34 PM

I have attached brew-install-nul-terminated.patch and tda-nul-terminated.patch that change format of .main file to have args NUL-terminated rather than space-separated.

Backward compatibility:

  • the patches changes cache ids, so caches with old .main format won't be reused

  • NUL characters in :main-opts previously were treated inconsistently: under Bash < 4.3 (e.g. MacOS) the remainder of the argument after NUL character was skipped, under Bash >= 4.3 (e.g. fresh Linux) NUL character was skipped, so we're not breaking anything here by reusing NUL as an argument delimiter.

OS compatibility:

  • Bash >= 2.04 (released ca. 2000, all relevant OSes have newer Bash)

Alternative approaches:

  • Escaping spaces instead of NUL-terminating lines

    • Upsides: none?

    • Downsides: Bash parsing code becomes much more convoluted (unless one resorts to eval with its associated issues)

  • Using .main2 files instead of changing hashes for existing cache entries

    • Upsides: existing cache entries that don't need a .main file can be reused after upgrade

    • Downsides: additional convoluted logic in clojure is needed to detect cache entries with .main file, treat them as stale, and remove old .main files after refreshing the cache.

Paulus Esterhazy December 4, 2018 at 10:27 PM

As an alternative to `eval`, the Bash code can read the string into an array manually, character by character. Arguments need to be separated by a delimiter.

Three possible delimiters come to mind:

  • NUL: won't work because `readarray` is not available in Bash3 (mandated by macOS) and NUL can't be stored in Bash variables

  • space

  • newline

As newlines are less likely to appear in main opts, that would be my choice. However, newlines might still occur so they should be escaped by the Clojure code using a backslash. In addition, literal backslashes need to be escaped as well.

I've done a proof of concept that this works with Bash3: https://github.com/clojure/brew-install/pull/3/files#diff-b3212e45e19f61de4754a755466b793f

If that sounds right, I'll update the patch.

Andrea Richiardi December 2, 2018 at 11:35 PM

Very useful scripts indeed (I mean try and demo)

Paulus Esterhazy December 2, 2018 at 8:24 PM

Another option is to check the input before eval using

Fixed

Details

Assignee

Reporter

Priority

Created March 17, 2018 at 9:16 PM
Updated January 4, 2022 at 2:41 PM
Resolved February 17, 2021 at 10:40 PM