diff --git a/README.md b/README.md index fdd6817..4880a59 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ are regenerated on each release — patches cannot be merged directly. Report problems (or send patches) to the author; accepted changes are applied to the development tree and appear in a following snapshot. -This snapshot was assembled from development commit `daad6273483a`. +This snapshot was assembled from development commit `f344a37d772c`. ## License diff --git a/com/Makefile b/com/Makefile index 5680833..7c6cf42 100644 --- a/com/Makefile +++ b/com/Makefile @@ -28,6 +28,20 @@ LIBRARY := $(LIBDIR)/libklammertext.so # Linker flags for commands COM_LDFLAGS := $(EXPORT_DYNAMIC) -Wl,-rpath,'$(ORIGIN)/../lib' -L$(LIBDIR) +# Windows: the application manifest (UTF-8 process code page) is linked into +# each command as a resource object; elsewhere there is nothing to add. +# (Only variables here: a rule before "all" would become the default goal. +# The Windows rules themselves follow the commands target below.) +ifneq (,$(filter MINGW% MSYS%,$(UNAME_S))) +MANIFEST_OBJ := manifest.o +# The DLLs beside the commands (makefile.env explains); with them, bin/ on +# PATH is all a Windows user needs besides Python. +RUNTIME_DLLS := $(addprefix $(BINDIR)/,libklammertext.so $(GCC_RUNTIME_DLLS)) +else +MANIFEST_OBJ := +RUNTIME_DLLS := +endif + # Dependency files for command sources DEPFILES := $(addsuffix .d,$(COMMANDS)) @@ -46,7 +60,16 @@ all : | mac sks # Separate target to build commands (called after dependencies are ready) .PHONY: commands -commands : $(BINCOMMANDS) +commands : $(BINCOMMANDS) $(RUNTIME_DLLS) + +ifneq (,$(filter MINGW% MSYS%,$(UNAME_S))) +manifest.o : klammertext.rc klammertext.manifest + windres $< $@ +$(BINDIR)/libklammertext.so : $(LIBRARY) | $(BINDIR) + cp $< $@ +$(BINDIR)/%.dll : $(GCC_BIN)%.dll | $(BINDIR) + cp $< $@ +endif # Ensure the output directory exists before linking into it. $(BINDIR) : @@ -63,14 +86,14 @@ sks : | mac $(MAKE) -C $(KS)/document # Explicit rules for each command - link against shared library, output to bin/ -$(BINDIR)/kdiag : kdiag.cpp $(LIBRARY) | $(BINDIR) - $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEPFLAGS) $(COM_LDFLAGS) $(LDFLAGS) $< -o $@ -lklammertext $(LDLIBS) +$(BINDIR)/kdiag : kdiag.cpp $(LIBRARY) $(MANIFEST_OBJ) | $(BINDIR) + $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEPFLAGS) $(COM_LDFLAGS) $(LDFLAGS) $< $(MANIFEST_OBJ) -o $@ $(KLAMMERTEXT_LIB) $(LDLIBS) -$(BINDIR)/kdesc : kdesc.cpp $(LIBRARY) | $(BINDIR) - $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEPFLAGS) $(COM_LDFLAGS) $(LDFLAGS) $< -o $@ -lklammertext $(LDLIBS) +$(BINDIR)/kdesc : kdesc.cpp $(LIBRARY) $(MANIFEST_OBJ) | $(BINDIR) + $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEPFLAGS) $(COM_LDFLAGS) $(LDFLAGS) $< $(MANIFEST_OBJ) -o $@ $(KLAMMERTEXT_LIB) $(LDLIBS) -$(BINDIR)/ktext : ktext.cpp $(LIBRARY) | $(BINDIR) - $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEPFLAGS) $(COM_LDFLAGS) $(LDFLAGS) $< -o $@ -lklammertext $(LDLIBS) +$(BINDIR)/ktext : ktext.cpp $(LIBRARY) $(MANIFEST_OBJ) | $(BINDIR) + $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEPFLAGS) $(COM_LDFLAGS) $(LDFLAGS) $< $(MANIFEST_OBJ) -o $@ $(KLAMMERTEXT_LIB) $(LDLIBS) # Out-of-tree system install (copies; the build tree stays intact). The # installed commands still need KLAMMERTEXT_HOME pointing at a Klammertext tree diff --git a/com/kdesc.cpp b/com/kdesc.cpp index 4286498..bba00f6 100644 --- a/com/kdesc.cpp +++ b/com/kdesc.cpp @@ -309,7 +309,7 @@ int main(int argc, char* argv[]) M.m_state.set( "K_input_dir", absolute_pathname(file_directory(input_filenames[0]))); } else { - M.m_state.set("K_input_dir", fs::current_path().string()); + M.m_state.set("K_input_dir", fs::current_path().generic_string()); } // --klammersets both lists and loads: with no words it enumerates the diff --git a/com/kdiag.cpp b/com/kdiag.cpp index 4a1d8e4..a1d81fc 100644 --- a/com/kdiag.cpp +++ b/com/kdiag.cpp @@ -74,7 +74,7 @@ int main(int argc, char* argv[]) } if (p("rewrite")) { - show_rewrite_rules = true; + set_show_rewrite_rules(true); } // kdiag deliberately has no --klammersets: a klammerset is reached here @@ -94,7 +94,7 @@ int main(int argc, char* argv[]) machine.m_state.set("K_target", Target_registry::general_name); machine.m_state.set("K_input_dir", fs::current_path().string()); std::string input = args.as_string("input"); - std::string command = construct_command_pathname(argv[0]); + std::string command = construct_command_pathname(argv[0]).generic_string(); // ONE authoritative katom list. process() returns the katoms but // registers nothing -- read() is what appends to m_katoms and extracts // the definitions -- so kdiag does that itself, in the order read() diff --git a/com/klammertext.manifest b/com/klammertext.manifest new file mode 100644 index 0000000..d8e39fc --- /dev/null +++ b/com/klammertext.manifest @@ -0,0 +1,14 @@ + + + + + + + UTF-8 + + + diff --git a/com/klammertext.rc b/com/klammertext.rc new file mode 100644 index 0000000..5c89f7e --- /dev/null +++ b/com/klammertext.rc @@ -0,0 +1,4 @@ +// Resource script for the Windows builds of the commands: embeds +// klammertext.manifest as the application manifest (resource type 24 is +// RT_MANIFEST, id 1 the process manifest). Compiled by windres; see com/Makefile. +1 24 "klammertext.manifest" diff --git a/doc/install/windows_source_install.md b/doc/install/windows_source_install.md new file mode 100644 index 0000000..4756236 --- /dev/null +++ b/doc/install/windows_source_install.md @@ -0,0 +1,305 @@ +# Klammertext source installation on Windows + +This document describes how to build and install Klammertext from source on +Windows 10 or 11, so that the three commands `ktext`, `kdesc` and `kdiag` +run as native Windows programs. Klammertext is developed on Linux and +macOS; Windows is a supported place to *use* it, and installing it there +takes more steps than on those systems, because Windows has no package +manager that supplies a C++ compiler and `make`. The steps below were +followed on a fresh Windows 11 machine and are the ones that worked. + +The installation uses three things a Windows user installs once: +**MSYS2**, which provides the compiler, `make` and a Unix-style shell for +building; **Python** from python.org, which Klammertext embeds; and **TeX +Live**, for PDF output. Everyday use needs only PowerShell. + + +## Prerequisites + +### Git and Python + +In PowerShell (search for "PowerShell" in the Start menu): + +```powershell +winget install --id Git.Git -e +winget install --id Python.Python.3.12 -e --override "/quiet InstallAllUsers=1 PrependPath=1" +``` + +The second command installs Python for all users into `C:\Program +Files\Python312` and puts it on the `Path`. Open a new PowerShell window +(the `Path` of an open one does not change) and install the image library +the `@image` klammer uses: + +```powershell +python -m pip install OpenImageIO +python -c "import OpenImageIO" +``` + +Note that the interpreter is called `python` on Windows. There is no +`python3`; the `python3` that Windows itself offers is a placeholder that +opens the Microsoft Store, and Klammertext does not use it. + +### MSYS2 and the compiler + +Install MSYS2 from https://www.msys2.org/ (or `winget install --id +MSYS2.MSYS2 -e`). It installs into `C:\msys64`. Open the **"MSYS2 UCRT64"** +shell from the Start menu — that one, not "MSYS2 MSYS" or "MINGW64": the +UCRT64 environment shares its C runtime with python.org's Python, which is +what lets the two be linked together. Update it and install the toolchain: + +```bash +pacman -Syu # if it closes the window, reopen the UCRT64 shell and run it again +pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-make make \ + mingw-w64-ucrt-x86_64-pkgconf mingw-w64-ucrt-x86_64-dlfcn diffutils +``` + +`diffutils` is only needed to run Klammertext's test suites. + +By default the MSYS2 shell does not see the programs Windows has on its +`Path`, which means it cannot see `git` or `python`. Tell it to, once, in +PowerShell, then reopen the UCRT64 shell: + +```powershell +setx MSYS2_PATH_TYPE inherit +``` + +Verify, in the UCRT64 shell: + +```bash +g++ --version # g++.exe (Rev4, Built by MSYS2 project) 16.x +python --version # Python 3.12.x (python.org's, from the Windows Path) +git --version +``` + + +## Clone the repository + +In the UCRT64 shell. Windows paths are written with forward slashes there, +`/c/Users/` for `C:\Users\`: + +```bash +cd /c/Users/ +git clone https://git.andykopra.com/ack/klammertext.git +cd klammertext +``` + +Git for Windows must not translate line endings: Klammertext's `.kt` +files, `.k` files and shell scripts are LF files. The clone above respects +that as long as `core.autocrlf` is not `true`; check with `git config +--get core.autocrlf` and, if it prints `true`, set it to `false` and clone +again. + + +## Environment variables + +Klammertext's runtime environment is one self-configuring file, the same +one Linux and macOS use. Source it from the UCRT64 shell's profile: + +```bash +echo 'source /c/Users//klammertext/env/runtime.env' >> ~/.bashrc +source ~/.bashrc +``` + +On Windows it sets `KLAMMERTEXT_HOME` and `TMPDIR` in the form Windows +programs understand (`C:/Users//klammertext`), adds `bin/` to `PATH`, +finds a TeX Live installed under `C:\Users\\external\texlive\` +(see below), and sets `PYTHON=python`. Nothing is edited by hand. + + +## Build + +In the UCRT64 shell, in the repository: + +```bash +make -C com -j +``` + +`-j` compiles in parallel; on a machine with 8 GB of memory or less the +optimizing compiler may run out of memory and report `out of memory +allocating`. If so, run `make -C com` without `-j`: slower, and it +finishes. + +This builds the library, the Standard Klammer Set's components and the +three commands into `bin\`, and copies the library and the compiler's +runtime DLLs there too, so `bin\` is self-contained. The Python binding is +built into `kpy\`. Verify: + +```bash +ktext -s '@eval 1 + 1 @' -d # prints 2 +ktext -s '@eval :shell echo hello | tr a-z A-Z @' -d # prints HELLO +``` + +The second line shows that `@eval :shell` runs a POSIX shell on Windows as +it does elsewhere: Klammertext ships one (`win\busybox\`), so a document's +`:shell` command means the same thing on every system. + +For a first document, create `hello.kt`: + +``` +@document +:structure article +:title Hello +:text +@s1 Hello, Klammertext @ + +This document was built from source on Windows. +@ +``` + +and render it: + +```bash +ktext hello.kt -t html # writes hello\index.html +``` + + +## TeX Live (for PDF output) + +The Standard Klammer Set renders PDF with **XeLaTeX**. Install TeX Live's +`scheme-small` plus the packages the SKS needs into +`C:\Users\\external\texlive\2026`, where `runtime.env` finds it. This +is a download of about 600 MB and takes twenty to thirty minutes. + +1. Download `install-tl.zip` from a CTAN mirror, for example + `https://ctan.math.illinois.edu/systems/texlive/tlnet/install-tl.zip`, + and unzip it. It unpacks into a folder named `install-tl-`. + +2. In that folder, create a file `texlive.profile` with this content, + replacing `` with your Windows user name: + + ``` + selected_scheme scheme-small + TEXDIR C:/Users//external/texlive/2026 + TEXMFLOCAL C:/Users//external/texlive/2026/texmf-local + TEXMFSYSCONFIG C:/Users//external/texlive/2026/texmf-config + TEXMFSYSVAR C:/Users//external/texlive/2026/texmf-var + TEXMFHOME C:/Users//texmf + TEXMFVAR C:/Users//.texlive2026/texmf-var + TEXMFCONFIG C:/Users//.texlive2026/texmf-config + tlpdbopt_install_docfiles 0 + tlpdbopt_install_srcfiles 0 + tlpdbopt_autobackup 0 + tlpdbopt_desktop_integration 0 + tlpdbopt_file_assocs 0 + tlpdbopt_w32_multi_user 0 + ``` + +3. In PowerShell, in that folder, run the installer without its graphical + interface, naming the same mirror: + + ```powershell + .\install-tl-windows.bat -no-gui -profile texlive.profile -repository https://ctan.math.illinois.edu/systems/texlive/tlnet + ``` + + Leave the window open until it reports that the installation is + complete. (A warning about `tlaunch.ini` is harmless.) + +4. Still in PowerShell, install the additional packages the SKS requires + beyond `scheme-small` — the same list `doc/install/texlive_additional_packages.sh` + installs on Linux and macOS — and rebuild the formats: + + ```powershell + $tl = "$env:USERPROFILE\external\texlive\2026\bin\windows" + $env:Path = "$tl;$env:Path" + & "$tl\tlmgr.bat" option repository https://ctan.math.illinois.edu/systems/texlive/tlnet + & "$tl\tlmgr.bat" install adjustbox collectbox collection-fontsrecommended enumitem fontaxes footmisc inconsolata layouts mdframed multirow needspace opensans pict2e textpos titlesec upquote zref + & "$tl\fmtutil-sys.exe" --all + ``` + + The second line matters: `tlmgr` and `fmtutil` start helper programs + such as `kpsewhich` by name and find them only through the `Path`. + +Open a new UCRT64 shell (so `runtime.env` runs again and finds the tree) +and verify: + +```bash +xelatex --version +ktext hello.kt -t pdf # writes hello.pdf +``` + + +## Using the commands from PowerShell + +Building needs the UCRT64 shell; using Klammertext does not. For everyday +use from PowerShell, put three directories on the `Path` and set +`KLAMMERTEXT_HOME`, once, as user environment variables (Settings ▸ +System ▸ About ▸ Advanced system settings ▸ Environment Variables, or with +`setx`): + +```powershell +setx KLAMMERTEXT_HOME "C:\Users\\klammertext" +setx Path "$env:Path;C:\Users\\klammertext\bin;C:\Users\\external\texlive\2026\bin\windows" +``` + +Python is already on the `Path` from its installer. Open a new PowerShell +window and `ktext`, `kdesc` and `kdiag` work there directly. Paths +Klammertext prints use forward slashes on every system; paths you type may +use either. + + +## Verifying the installation + +In the UCRT64 shell: + +```bash +ktext -s '@eval 1 + 1 @' -d # Python evaluation +ktext -s '@eval :shell date @' -d # shell evaluation (the bundled sh) +kdesc -t # the targets +kdesc --typeface # the fonts Klammertext ships +ktext hello.kt -t html # html +ktext hello.kt -t pdf # pdf, requires TeX Live + +make -C $KLAMMERTEXT_HOME/tst test # the engine's test suites +make -C $KLAMMERTEXT_HOME/sks/tst test # the Standard Klammer Set's suites (needs TeX Live) +``` + +A few suite cases report `SKIP` on Windows for things the MSYS2 shell +cannot provide, such as a real terminal for the colour tests or a +symbolic link; that is expected. + + +## Updating + +In the UCRT64 shell: + +```bash +cd $KLAMMERTEXT_HOME +git pull +make -C com -j +``` + + +## Troubleshooting + +**The command window closes at once, or "The code execution cannot proceed +because python312.dll was not found".** Python's directory is not on the +`Path` of that window. Reinstall Python with "Add to PATH" selected, or add +`C:\Program Files\Python312` to the `Path` yourself, and open a new window. + +**`import OpenImageIO` fails with "DLL load failed while importing +OpenImageIO".** The OpenImageIO package needs the Microsoft Visual C++ +runtime, which nearly every Windows machine already has but a freshly +installed one may not. Install it from +https://aka.ms/vs/17/release/vc_redist.x64.exe and try the import again. + +**"libklammertext.so was not found" or "libstdc++-6.dll was not found".** +`bin\` is not on the `Path`, or the build did not finish: `make -C com -j` +places the library and the runtime DLLs in `bin\` beside the commands. + +**"The shell Klammertext runs sh commands with on Windows is missing".** +`win\busybox\busybox64u.exe` is part of the repository; the clone is +incomplete or `KLAMMERTEXT_HOME` points elsewhere. + +**"The 'xelatex' command required to make PDF files is not installed".** +TeX Live's `bin\windows` directory is not on the `Path` of the shell you +are using. In the UCRT64 shell `runtime.env` adds it when the tree is under +`C:\Users\\external\texlive\`; in PowerShell add it as shown +above. + +**`git` or `python` are "command not found" in the UCRT64 shell.** +`MSYS2_PATH_TYPE` is not set to `inherit`, or the shell was opened before +you set it. Set it and open a new UCRT64 shell. + +**The build is slow.** Windows Defender scans every file the compiler +writes. Excluding the repository folder from real-time scanning speeds the +build up considerably; it is not required. diff --git a/env/makefile.env b/env/makefile.env index bac46e9..cea6efd 100644 --- a/env/makefile.env +++ b/env/makefile.env @@ -27,9 +27,20 @@ else COMPILER ?= gcc # gcc | clang endif -# Python: version- and platform-agnostic, no hardcoded paths. +# Python: version- and platform-agnostic, no hardcoded paths. On Windows the +# interpreter is python.org's (the one PyPI wheels -- OpenImageIO -- are built +# for); it ships no python3-config, so its prefix is asked of the interpreter +# itself and its import library named by version (libs/python312.lib). +ifneq (,$(filter MINGW% MSYS%,$(UNAME_S))) +PYTHON := python +PYTHON_PREFIX := $(shell cygpath -m "$$($(PYTHON) -c 'import sys; print(sys.base_prefix)')") +PYTHON_INC := -I"$(PYTHON_PREFIX)/Include" +PYTHON_LIB := -L"$(PYTHON_PREFIX)/libs" -l$(shell $(PYTHON) -c 'import sys; print("python%d%d" % sys.version_info[:2])') +else +PYTHON := python3 PYTHON_INC := $(shell python3-config --includes) PYTHON_LIB := $(shell python3-config --ldflags --embed) +endif # Flags common to all platforms. CPPFLAGS = -I$(KLAMMERTEXT_HOME)/mac $(PYTHON_INC) @@ -52,6 +63,29 @@ SHARED = -dynamiclib SONAME = -install_name @rpath/$(notdir $@) ORIGIN := @loader_path EXPORT_DYNAMIC = -Wl,-export_dynamic +else ifneq (,$(filter MINGW% MSYS%,$(UNAME_S))) +# ---------- Windows (MSYS2, UCRT64 environment; native PE binaries) ---------- +# uname says MINGW64_NT-... under UCRT64 too. The compilers come from the +# environment's PATH (/ucrt64/bin). libdl.a is the mingw-w64-dlfcn shim over +# LoadLibrary, linked statically so no libdl.dll travels with the products. +# PE has no soname and no rpath; a DLL is found through PATH or +# the executable's own directory, and GNU ld's -l search list on PE does not +# include ".so", hence the exact-name form below. +GCC := g++ +CLANG := clang++ +LDFLAGS = +LDLIBS = -l:libdl.a $(if $(NOPYTHON),,$(PYTHON_LIB)) +SHARED = -shared +SONAME = +ORIGIN := $$ORIGIN +EXPORT_DYNAMIC = -Wl,--export-all-symbols +KLAMMERTEXT_LIB = -l:libklammertext.so +# A Windows program finds a DLL through PATH or its own directory, never an +# rpath. So every directory that holds a program or a Python extension also +# holds the DLLs it needs: libklammertext.so and the three gcc runtime DLLs +# the toolchain links (from the compiler's own bin/). com/ and kpy/ copy them. +GCC_BIN := $(dir $(shell cygpath -m "$$(command -v g++)")) +GCC_RUNTIME_DLLS := libstdc++-6.dll libgcc_s_seh-1.dll libwinpthread-1.dll else # ---------- Linux ---------- GCC := /usr/bin/g++ @@ -63,6 +97,8 @@ SONAME = -Wl,-soname,$(notdir $@) ORIGIN := $$ORIGIN EXPORT_DYNAMIC = -rdynamic endif +# How a link line names libklammertext (set above for Windows). +KLAMMERTEXT_LIB ?= -lklammertext # Resolve the compiler choice (gcc by default; clang with COMPILER=clang). CXX := $(if $(filter clang,$(COMPILER)),$(CLANG),$(GCC)) diff --git a/env/runtime.env b/env/runtime.env index f94ca74..3752a22 100644 --- a/env/runtime.env +++ b/env/runtime.env @@ -20,6 +20,19 @@ export KLAMMERTEXT_HOME="$(cd "$(dirname "$_kt_self")/.." && pwd)" unset _kt_self _kt_uname="$(uname -s)" +# Windows under MSYS2: the commands are native programs and never understand +# MSYS's /c/... spelling, so KLAMMERTEXT_HOME and TMPDIR reach them in Windows +# form (C:/..., cygpath -m). bash's own PATH keeps the POSIX form -- a colon +# inside a PATH entry splits it. Python (python.org's) is the user's to put on +# PATH; an MSYS2 login shell does not inherit the Windows PATH by default. +_kt_home_posix="$KLAMMERTEXT_HOME" +case "$_kt_uname" in + MINGW*|MSYS*) + export KLAMMERTEXT_HOME="$(cygpath -m "$KLAMMERTEXT_HOME")" + export TMPDIR="$(cygpath -m "${TMPDIR:-/tmp}")" + export PYTHON="${PYTHON:-python}" # python.org installs no python3 + ;; +esac # --- KLAMMERTEXT_TEXLIVE_BIN: newest installed TeX Live under ~/external ------- # Klammertext's TeX Live installs are named by 4-digit year (2024, 2025, 2026). @@ -27,20 +40,27 @@ _kt_uname="$(uname -s)" # are never selected; sort -V then picks the newest year. if [ "$_kt_uname" = "Darwin" ]; then _kt_arch="universal-darwin" +elif case "$_kt_uname" in MINGW*|MSYS*) true;; *) false;; esac; then + _kt_arch="windows" # TeX Live's bin/windows else _kt_arch="$(uname -m)-linux" # e.g. x86_64-linux fi # Use `find` with a -path pattern, NOT a shell glob: on macOS/zsh an unmatched # glob (no texlive installed) is a hard "no matches found" error, whereas find # just returns nothing. The -path pattern keeps the 4-digit-year restriction. -_kt_tl="$(find "$HOME/external/texlive" -maxdepth 3 -type d \ +# Windows: a user installs under the Windows home, %USERPROFILE%\external +# (MSYS2's $HOME is C:\msys64\home\, which no Windows guide would name). +_kt_tlroot="$HOME/external/texlive" +case "$_kt_uname" in MINGW*|MSYS*) _kt_tlroot="$(cygpath -u "$USERPROFILE")/external/texlive";; esac +_kt_tl="$(find "$_kt_tlroot" -maxdepth 3 -type d \ -path "*/[0-9][0-9][0-9][0-9]/bin/$_kt_arch" 2>/dev/null \ | sort -V | tail -1)" [ -n "$_kt_tl" ] && export KLAMMERTEXT_TEXLIVE_BIN="$_kt_tl" -unset _kt_tl _kt_arch +unset _kt_tl _kt_arch _kt_tlroot # --- PATH (TeX Live appended only if one was found) --------------------------- -export PATH="$KLAMMERTEXT_HOME/bin:$KLAMMERTEXT_HOME/tst${KLAMMERTEXT_TEXLIVE_BIN:+:$KLAMMERTEXT_TEXLIVE_BIN}:$PATH" +export PATH="$_kt_home_posix/bin:$_kt_home_posix/tst${KLAMMERTEXT_TEXLIVE_BIN:+:$KLAMMERTEXT_TEXLIVE_BIN}:$PATH" +unset _kt_home_posix # --- The Python binding (kpy/) ----------------------------------------------- # "import klammertext" with no setup, as the container image already arranges diff --git a/kpy/Makefile b/kpy/Makefile index fb93ec4..e26353b 100644 --- a/kpy/Makefile +++ b/kpy/Makefile @@ -21,7 +21,7 @@ include $(K)/env/makefile.env LIBDIR := $(K)/lib LIBRARY := $(LIBDIR)/libklammertext.so -EXT_SUFFIX := $(shell python3-config --extension-suffix) +EXT_SUFFIX := $(shell $(PYTHON) -c 'import sysconfig; print(sysconfig.get_config_var("EXT_SUFFIX"))') MODULE := klammertext$(EXT_SUFFIX) # "../lib": the module sits in $(K)/kpy, one level below $(K). @@ -34,7 +34,16 @@ DEPFLAGS = -MMD -MP -MF $(@:.o=.d) .PHONY: all clean -all : $(MODULE) +# Windows: the module's dependent DLLs sit beside it (Python loads an +# extension's dependencies from the extension's own directory, never from +# PATH or an rpath -- there is none on PE); see makefile.env. +ifneq (,$(filter MINGW% MSYS%,$(UNAME_S))) +BESIDE_DLLS := libklammertext.so $(GCC_RUNTIME_DLLS) +else +BESIDE_DLLS := +endif + +all : $(MODULE) $(BESIDE_DLLS) %.o : %.cpp @echo Compiling $< @@ -42,10 +51,17 @@ all : $(MODULE) $(MODULE) : $(OBJECTS) $(LIBRARY) $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(KPY_LDFLAGS) $(LDFLAGS) \ - -o $@ $(OBJECTS) -lklammertext $(LDLIBS) + -o $@ $(OBJECTS) $(KLAMMERTEXT_LIB) $(LDLIBS) + +ifneq (,$(filter MINGW% MSYS%,$(UNAME_S))) +libklammertext.so : $(LIBRARY) + cp $< $@ +%.dll : $(GCC_BIN)%.dll + cp $< $@ +endif clean : - rm -f $(OBJECTS) $(DEPFILES) klammertext.*.so *~ + rm -f $(OBJECTS) $(DEPFILES) klammertext.*.so klammertext.*.pyd libklammertext.so *.dll *~ rm -rf __pycache__ -include $(DEPFILES) diff --git a/kpy/klammertext.cpp b/kpy/klammertext.cpp index c1b521b..a4f4a3b 100644 --- a/kpy/klammertext.cpp +++ b/kpy/klammertext.cpp @@ -215,9 +215,12 @@ int instances = 0; // module, so a relative @read in read() text resolves against the directory // the script runs in -- the same rule ktext applies to -s input, and set at // each read() because a script may chdir between two of them. +// The command's identity, through the library (set_command_identity): on +// Windows an assignment to error.h's inline variables from this module +// would land in this module's own copies, not the library's. void set_string_source() { - command_pathname = (fs::current_path() / "klammertext").string(); + set_command_identity("klammertext", (fs::current_path() / "klammertext").generic_string()); } PyObject* Machine_new(PyTypeObject* type, PyObject*, PyObject*) @@ -607,7 +610,6 @@ PyMODINIT_FUNC PyInit_klammertext(void) return nullptr; } // What an error report names as the command. - command_name = "klammertext"; set_string_source(); const char* home = std::getenv(klammertext_home_var.c_str()); PyObject* home_obj = home ? PyUnicode_FromString(home) : Py_None; diff --git a/mac/argv.h b/mac/argv.h index 3338bf7..6433fb2 100644 --- a/mac/argv.h +++ b/mac/argv.h @@ -134,5 +134,5 @@ public: // these, so a shell-quoted filename containing spaces stays one // element; the space-joined m_value remains only for get()/describe(). std::map> m_vectors {}; - long unsigned int m_syntax_size = 0; + std::size_t m_syntax_size = 0; }; diff --git a/mac/command.cpp b/mac/command.cpp index 87442ce..238b691 100644 --- a/mac/command.cpp +++ b/mac/command.cpp @@ -1,3 +1,7 @@ +#ifdef _WIN32 +#include +#include +#endif #include #include "argv.h" @@ -21,11 +25,24 @@ fs::path construct_command_pathname(char* command) return fs::current_path() / fs::path(command).filename(); } +void set_command_identity(const std::string& name, const std::string& pathname) +{ + command_name = name; + command_pathname = pathname; +} + void set_verbose_level(int argc, char* argv[]) { +#ifdef _WIN32 + // Klammertext writes LF on every system: a document a Windows user pipes + // or redirects must not gain CR, and the suites read the streams byte for + // byte. The C runtime's text mode would append it to every line. + _setmode(_fileno(stdout), _O_BINARY); + _setmode(_fileno(stderr), _O_BINARY); +#endif //command_name = absolute_pathname(argv[0]); - command_name = std::string(argv[0]); - command_pathname = construct_command_pathname(argv[0]); + command_name = fs::path(argv[0]).generic_string(); + command_pathname = construct_command_pathname(argv[0]).generic_string(); //std::cout << "set_verbose_level: " << command_pathname << "\n"; Argv args {}; @@ -140,7 +157,7 @@ void set_command_state( if (!input_filenames.empty()) { S.set("K_input_dir", absolute_pathname(file_directory(input_filenames[0]))); } else { - S.set("K_input_dir", fs::current_path().string()); + S.set("K_input_dir", fs::current_path().generic_string()); } } @@ -178,7 +195,7 @@ void load_klammersets(Machine& machine, const strings_t& symbols) continue; } std::string klammerset_filename = resolve_klammerset_symbol( - symbol, machine.m_state.value("K_input_dir"), Locator::none()).string(); + symbol, machine.m_state.value("K_input_dir"), Locator::none()).generic_string(); // Symbol -> file is the search path's answer, and the search // path has three stages with shadowing: the file it landed on is // exactly what a user cannot read off "--klammersets x". diff --git a/mac/command.h b/mac/command.h index 35fb21d..9299e44 100644 --- a/mac/command.h +++ b/mac/command.h @@ -10,6 +10,13 @@ class Machine; void set_verbose_level(int argc, char* argv[]); +// What an error report and the -s pseudo source name as the command, set +// from OUTSIDE the library (the Python binding). It is a function, not an +// assignment to the header's inline variables, because on Windows every +// module -- the DLL, an extension module -- has its OWN copy of an inline +// variable: an assignment made in the binding never reached the copy the +// library's error reports read. On Linux the two are one variable. +void set_command_identity(const std::string& name, const std::string& pathname); bool show_usage(int argc, char* argv[]); fs::path construct_command_pathname(char* command); diff --git a/mac/coverage.cpp b/mac/coverage.cpp index a480cd1..3b29ae1 100644 --- a/mac/coverage.cpp +++ b/mac/coverage.cpp @@ -139,8 +139,10 @@ std::string short_path(const std::string& path) std::error_code ec {}; fs::path home_real = fs::weakly_canonical(fs::path(home), ec); fs::path path_real = fs::weakly_canonical(fs::path(path), ec); - std::string prefix = (ec ? std::string(home) : home_real.string()) + "/"; - const std::string& subject = ec ? path : path_real.string(); + // Both in the generic spelling ('/'): on Windows weakly_canonical + // answers with backslashes while the report's paths use '/'. + std::string prefix = (ec ? fs::path(home).generic_string() : home_real.generic_string()) + "/"; + const std::string subject = ec ? fs::path(path).generic_string() : path_real.generic_string(); if (subject.size() > prefix.size() && subject.compare(0, prefix.size(), prefix) == 0) { return subject.substr(prefix.size()); diff --git a/mac/error.cpp b/mac/error.cpp index e97c192..f666b16 100644 --- a/mac/error.cpp +++ b/mac/error.cpp @@ -11,7 +11,7 @@ bool display_source(const std::string& filename_arg) return false; } else { fs::path filename = fs::path(filename_arg).filename(); - return sks_commands.count(filename) == 0; + return sks_commands.count(filename.string()) == 0; } } diff --git a/mac/eval.cpp b/mac/eval.cpp index 3b0a0b4..4aceb51 100644 --- a/mac/eval.cpp +++ b/mac/eval.cpp @@ -10,7 +10,16 @@ #include #include #include +#ifdef _WIN32 +// mingw-w64 has no : there pclose() returns the child's exit code +// itself, not a wait status, and no signal can have killed it. +#define WIFEXITED(s) ((s) != -1) +#define WEXITSTATUS(s) (s) +#define WIFSIGNALED(s) 0 +#define WTERMSIG(s) 0 +#else #include +#endif // Run a shell command and return its STANDARD OUTPUT, which becomes document // text. Two things the first version did not do, both of them the same defect @@ -51,7 +60,8 @@ std::string shell(State state, std::string command, Locator loc) // The braces keep the redirection outside the writer's command, so a // command containing its own pipeline or redirection still works. std::string wrapped = "{ " + command + " ; } 2>" + q_(err_path); - FILE* pipe = popen(wrapped.c_str(), "r"); + Shell_command sh_cmd(wrapped, loc); + FILE* pipe = popen(sh_cmd.line().c_str(), "r"); if (!pipe) { fs::remove(err_path); throw Environment_error( @@ -129,7 +139,9 @@ std::string run_haskell(const std::string& hsfile, Locator loc) err_path = err_template.data(); std::string command = "runghc " + q_(hsfile) + " 2>" + q_(err_path); - FILE* pipe = popen(command.c_str(), "r"); + // Through the same sh as :shell: the quoting is sh quoting. + Shell_command sh_cmd(command, loc); + FILE* pipe = popen(sh_cmd.line().c_str(), "r"); if (!pipe) { fs::remove(err_path); throw Environment_error("Could not run runghc.", loc); diff --git a/mac/file.cpp b/mac/file.cpp index 8a16bc5..4d11ff8 100644 --- a/mac/file.cpp +++ b/mac/file.cpp @@ -33,7 +33,7 @@ std::string absolute_pathname(const std::string& filename, const std::string& ba { fs::path p(filename); if (filename.empty()) { - return fs::current_path().string(); + return fs::current_path().generic_string(); } if (!base.empty()) { // Resolve filename relative to base's directory @@ -41,7 +41,7 @@ std::string absolute_pathname(const std::string& filename, const std::string& ba fs::path base_dir = fs::is_directory(b) ? b : b.parent_path(); p = base_dir / p; } - return fs::absolute(p).string(); + return fs::absolute(p).generic_string(); } std::string relative_pathname(const std::string& filename) @@ -70,6 +70,66 @@ size_t count_substrings(const std::string& text, const std::string& substring) { return count; } +std::string home_directory() +{ + if (const char* home = std::getenv("HOME"); home && *home) { + return home; + } +#ifdef _WIN32 + if (const char* profile = std::getenv("USERPROFILE"); profile && *profile) { + return profile; + } +#endif + return {}; +} + +std::string find_in_path(const std::string& program) +{ + const char* path = std::getenv("PATH"); + if (path == nullptr) { + return {}; + } + std::stringstream ss(path); + std::string dir; + while (std::getline(ss, dir, path_list_separator)) { + if (dir.empty()) { + continue; + } + fs::path candidate = fs::path(dir) / program; + std::error_code ec {}; + if (fs::is_regular_file(candidate, ec)) { + return candidate.generic_string(); + } +#ifdef _WIN32 + fs::path exe = candidate; + exe += ".exe"; + if (fs::is_regular_file(exe, ec)) { + return exe.generic_string(); + } +#endif + } + return {}; +} + +std::vector directory_list(const char* variable, const std::string& default_subdir) +{ + std::string paths {}; + if (const char* env = std::getenv(variable); env && *env) { + paths = env; + } else if (std::string home = home_directory(); !home.empty()) { + paths = home + "/" + default_subdir; + } + std::vector result {}; + std::stringstream ss(paths); + std::string dir; + while (std::getline(ss, dir, path_list_separator)) { + if (!dir.empty()) { + result.push_back(dir); + } + } + return result; +} + fs::path relative_to_cwd(const fs::path& input) { const auto base = fs::current_path(); @@ -289,8 +349,10 @@ std::string string_from_file(const std::string& pathname, bool strip_surrounding void string_to_file(const std::string& pathname, std::string contents) { - fs::create_directories(file_directory(fs::absolute(pathname))); - std::ofstream out(pathname); + fs::create_directories(file_directory(fs::absolute(pathname).string())); + // Binary: the text Klammertext writes ends its lines with LF on every + // system; Windows' text mode would turn each into CRLF. + std::ofstream out(pathname, std::ios::binary); if (!out) { throw File_error("Could not write file " + pathname); } @@ -319,7 +381,7 @@ strings_t get_files_in_directory(const std::string& dir) for (const auto& entry : fs::directory_iterator(dir)) { if (entry.is_regular_file()) { // std::cout << entry.path().filename() << std::endl; - result.push_back(entry.path().filename()); + result.push_back(entry.path().filename().string()); } } } catch (const fs::filesystem_error& ex) { @@ -456,8 +518,11 @@ strings_t get_sks_directories(const std::string& s, bool include_argument) std::string symbolic_klammertext_filename(std::string filename) { + // Both in the generic spelling: a Windows KLAMMERTEXT_HOME may be written + // with either separator, and a path Klammertext prints uses '/'. return string_replace( - filename, get_env_var("KLAMMERTEXT_HOME"), "$KLAMMERTEXT_HOME"); + fs::path(filename).generic_string(), + fs::path(get_env_var("KLAMMERTEXT_HOME")).generic_string(), "$KLAMMERTEXT_HOME"); } diff --git a/mac/file.h b/mac/file.h index d7211d7..522aba2 100644 --- a/mac/file.h +++ b/mac/file.h @@ -16,6 +16,28 @@ std::string relative_pathname(const std::string& filename, const std::string& ba fs::path relative_to_cwd(const fs::path& input); +// The separator of a directory list in an environment variable +// (KLAMMERTEXT_FONTS, KLAMMERTEXT_KLAMMERSETS): ';' on Windows, where a drive +// letter puts a colon inside every absolute path, ':' everywhere else -- the +// convention of PATH on each system. +#ifdef _WIN32 +constexpr char path_list_separator = ';'; +#else +constexpr char path_list_separator = ':'; +#endif +// The user's home directory as the system names it: HOME, and on Windows +// USERPROFILE when HOME is unset (a plain PowerShell or cmd sets no HOME; +// an MSYS2 shell does). Empty when neither is set. +std::string home_directory(); +// The first PROGRAM on PATH (split on path_list_separator; on Windows the +// name also tried with ".exe"), or "" when none: what "which" answers, without +// spawning a shell -- on Windows popen()'s cmd.exe has no "which". +std::string find_in_path(const std::string& program); +// The directories listed in an environment variable, split on +// path_list_separator, empty entries dropped; DEFAULT_SUBDIR under the home +// directory when the variable is unset or empty. +std::vector directory_list(const char* variable, const std::string& default_subdir); + bool file_exists(const std::string& pathname, bool error_if_not=false, bool is_directory=false); // Text to its lines, and a file to (its whole text, its lines). These diff --git a/mac/font_store.cpp b/mac/font_store.cpp index 1d9cdb3..9f32580 100644 --- a/mac/font_store.cpp +++ b/mac/font_store.cpp @@ -23,12 +23,20 @@ namespace fs = std::filesystem; +// The interpreter spawned for the font-metric script: python.org's Windows +// build installs "python" only; Linux and macOS name it "python3". +#ifdef _WIN32 +static const char* const python_interpreter = "python"; +#else +static const char* const python_interpreter = "python3"; +#endif + // A filesystem extension lowercased for case-insensitive matching, so a // font named Foo.TTF or Foo.OTF is recognized like Foo.ttf / Foo.otf. static std::string lower_extension(const fs::path& path) { - std::string ext = path.extension(); + std::string ext = path.extension().string(); for (char& c : ext) { c = std::tolower(static_cast(c)); } @@ -49,29 +57,14 @@ std::string name_to_dirname(const std::string& name) } -// The directories searched for installed fonts: the colon-separated -// KLAMMERTEXT_FONTS environment variable, or ~/.klammertext/fonts when it -// is not set. Directories are searched in listed order, and the default -// fonts in $KLAMMERTEXT_HOME/fnt are searched last, so an installed font -// can deliberately shadow a default one. +// The directories searched for installed fonts: the KLAMMERTEXT_FONTS +// environment variable (a list in the system's PATH convention: ':' or, on +// Windows, ';'), or ~/.klammertext/fonts when it is not set. Directories are +// searched in listed order, and the default fonts in $KLAMMERTEXT_HOME/fnt are +// searched last, so an installed font can deliberately shadow a default one. strings_t installed_font_dirs() { - strings_t result {}; - std::string paths {}; - const char* env = std::getenv("KLAMMERTEXT_FONTS"); - if (env && *env) { - paths = env; - } else if (const char* home = std::getenv("HOME"); home && *home) { - paths = std::string(home) + "/.klammertext/fonts"; - } - std::stringstream ss(paths); - std::string dir; - while (std::getline(ss, dir, ':')) { - if (!dir.empty()) { - result.push_back(dir); - } - } - return result; + return directory_list("KLAMMERTEXT_FONTS", ".klammertext/fonts"); } std::string default_font_dir() @@ -151,7 +144,7 @@ std::vector fonts_in_directory(const std::string& base) for (auto& entry : fs::directory_iterator(base)) { if (entry.path().extension() == ".css" && fs::is_directory(base + "/" + entry.path().stem().string())) { - names.push_back(entry.path().stem()); + names.push_back(entry.path().stem().string()); } } std::sort(names.begin(), names.end()); @@ -199,10 +192,12 @@ static void extract_font_metrics(Resolved_font& font) if (!fs::exists(ttf_path)) return; // Extract both x-height and cap-height ratios from OS/2 table + // The path goes inside a Python literal: forward slashes, which Python + // opens on every system, where a Windows backslash would be an escape. std::string script = - "python3 -c \"" + std::string(python_interpreter) + " -c \"" "import struct; " - "f = open('" + ttf_path + "', 'rb'); " + "f = open('" + fs::path(ttf_path).generic_string() + "', 'rb'); " "_, n = struct.unpack('>IH', f.read(6)); " "f.read(6); " "t = {};\n" @@ -219,6 +214,9 @@ static void extract_font_metrics(Resolved_font& font) "print(f'{xh/upm:.4f} {ch/upm:.4f}') if ver >= 2 else None; " "f.close()\""; std::string result = trim(exec(script.c_str())); + // A DERIVED value a designer cannot read off the font: the two ratios + // (or nothing, when the script failed) decide the html size normalization. + (void)K::log(3, "Font metrics of " + ttf_path + ": " + (result.empty() ? std::string("(none)") : result)); if (!result.empty()) { try { auto pos = result.find(' '); @@ -418,7 +416,7 @@ std::vector classify_font_files(const std::string& directory) for (auto& entry : fs::recursive_directory_iterator(directory)) { std::string ext = lower_extension(entry.path()); if (entry.is_regular_file() && (ext == ".ttf" || ext == ".otf")) { - result.push_back(classify_font_file(entry.path())); + result.push_back(classify_font_file(entry.path().string())); } } return result; @@ -521,7 +519,7 @@ std::string write_font_samples(const std::string& output_dir, const std::string& install_fonts(source_dir, output_dir + "/fonts"); for (auto& entry : fs::directory_iterator(output_dir + "/fonts")) { if (entry.path().extension() == ".css") { - std::string css = string_from_file(entry.path()); + std::string css = string_from_file(entry.path().string()); std::smatch match {}; if (std::regex_search(css, match, std::regex(R"(font-family:\s*'([^']+)')"))) { diff --git a/mac/katomize.cpp b/mac/katomize.cpp index 5374473..558af9f 100644 --- a/mac/katomize.cpp +++ b/mac/katomize.cpp @@ -160,3 +160,8 @@ void warn_unparsed_katoms(katom_list& katoms, bool warn) } } } + +void set_show_rewrite_rules(bool on) +{ + show_rewrite_rules = on; +} diff --git a/mac/katomize.h b/mac/katomize.h index 85bf4c5..314639a 100644 --- a/mac/katomize.h +++ b/mac/katomize.h @@ -82,6 +82,10 @@ katom_list katomize_lines(const strings_t& lines, const std::string& source, // expand_compound_katom() and attributed to whichever rule the loop // happened to be holding, so it always read as "bar precedence". inline bool show_rewrite_rules = false; +// Set it through this, never by assignment from a command: on Windows the +// command's copy of an inline variable is not the library's (see +// set_command_identity in command.h). +void set_show_rewrite_rules(bool on); // Warn about text that matched no katom type and that no rule could divide // -- but only where it survives processing: text removed by #, ##, or diff --git a/mac/klammerset_registry.cpp b/mac/klammerset_registry.cpp index a07f570..a65ac88 100644 --- a/mac/klammerset_registry.cpp +++ b/mac/klammerset_registry.cpp @@ -151,7 +151,7 @@ std::vector klammerset_search_dirs(const std::string& local_dir) // A repeated directory (e.g. the local stage already IS // $KLAMMERTEXT_HOME) adds nothing and clutters error messages. auto push_unique = [&result](const std::string& dir) { - std::string canonical = fs::weakly_canonical(dir).string(); + std::string canonical = fs::weakly_canonical(dir).generic_string(); if (!is_in(canonical, result)) { result.push_back(canonical); } @@ -171,22 +171,10 @@ std::vector klammerset_search_dirs(const std::string& local_dir) // a document could extend its own search path between one declaration and // the next. Nothing intended that -- it fell out of a fresh getenv and an // in-process interpreter. - static const std::string paths = [] { - const char* env = std::getenv("KLAMMERTEXT_KLAMMERSETS"); - if (env && *env) { - return std::string(env); - } - if (const char* home = std::getenv("HOME"); home && *home) { - return std::string(home) + "/.klammertext/klammersets"; - } - return std::string(); - }(); - std::stringstream ss(paths); - std::string dir; - while (std::getline(ss, dir, ':')) { - if (!dir.empty()) { - push_unique(dir); - } + static const strings_t installed = + directory_list("KLAMMERTEXT_KLAMMERSETS", ".klammertext/klammersets"); + for (const std::string& dir : installed) { + push_unique(dir); } if (const char* kthome = std::getenv(klammertext_home_var.c_str()); kthome && *kthome) { push_unique(kthome); @@ -280,11 +268,11 @@ std::string describe_klammerset_search(const std::string& local_dir, int margin) if (!is_klammerset_symbol(symbol) || !fs::is_regular_file(declaration)) { continue; } - std::string line = tab + symbol + sp_arrow + declaration.string(); + std::string line = tab + symbol + sp_arrow + declaration.generic_string(); if (first_hit.count(symbol)) { line += " (shadowed by " + first_hit[symbol] + ")"; } else { - first_hit[symbol] = declaration.string(); + first_hit[symbol] = declaration.generic_string(); } // What a set IS, under where it lives: the symbol and the path // say which file would load, and nothing at all about whether diff --git a/mac/locator.cpp b/mac/locator.cpp index beb5e1b..c009d44 100644 --- a/mac/locator.cpp +++ b/mac/locator.cpp @@ -12,14 +12,14 @@ std::string abbreviate_location( { std::string result = location; fs::path basename = fs::path(location).filename(); - if (sks_commands.contains(basename)) { - result = basename; + if (sks_commands.contains(basename.string())) { + result = basename.string(); } else { if (make_map) { if (relpath.count(location)) { result = relpath[location]; } else { - result = fs::relative(location, fs::current_path()); + result = fs::relative(location, fs::current_path()).generic_string(); relpath[location] = result; } } @@ -29,7 +29,7 @@ std::string abbreviate_location( Locator::Locator(const fs::path& filename, int line, int chr) - : m_filename(filename) + : m_filename(filename.generic_string()) , m_line(line) , m_chr(chr) { @@ -49,7 +49,7 @@ std::string Locator::str(bool relative) const // relative_pathname(m_filename, fs::current_path().string()) << ":" std::string filename = m_filename; if (relative) { - filename = relative_to_cwd(m_filename); + filename = relative_to_cwd(m_filename).generic_string(); } std::stringstream ss {}; ss << "[" << filename << ":" @@ -63,7 +63,7 @@ std::string Locator::desc(bool relative) const // relative_pathname(m_filename, fs::current_path().string()) << ":" std::string filename = m_filename; if (relative) { - filename = relative_to_cwd(m_filename); + filename = relative_to_cwd(m_filename).generic_string(); } std::stringstream ss {}; ss << filename << ", line " << m_line << ", character " << m_chr + 1; diff --git a/mac/locator.h b/mac/locator.h index c64866e..b8222b1 100644 --- a/mac/locator.h +++ b/mac/locator.h @@ -19,7 +19,7 @@ public: // toolchains (e.g. Homebrew GCC on macOS); guard against // fs::path(nullptr) -> strlen(NULL). : m_filename(location.file_name() - ? fs::absolute(location.file_name()) : fs::path{}) + ? fs::absolute(location.file_name()).generic_string() : std::string{}) , m_line(int(location.line())) , m_chr(int(location.column())) {}; diff --git a/mac/machine.cpp b/mac/machine.cpp index 53fefe6..a139c44 100644 --- a/mac/machine.cpp +++ b/mac/machine.cpp @@ -491,7 +491,7 @@ void Machine::read(const fs::path& pathname) // :cpp libraries live next to the file that uses them). m_state.add_search_dir(fs::absolute(pathname).parent_path().string()); m_state.open_frame("Machine state: " + pathname.string()); - input(string_from_file(pathname), pathname.string()); + input(string_from_file(pathname.string()), pathname.generic_string()); m_sources.push_back(pathname); } @@ -519,7 +519,7 @@ void Machine::expand_read_katoms( bool klammers, bool eval, bool cond, bool read) { (void)K::log(3); - current_filename = resolve_relative_to(current_filename); + current_filename = resolve_relative_to(current_filename).generic_string(); // msg() << "current_filename: " << current_filename << "\n"; if (std::find_if(katoms.begin(), katoms.end(), begin_read) != katoms.end()) { (void)K::log(3); @@ -553,11 +553,11 @@ void Machine::expand_read_katoms( input_filename = fs::canonical(input_filename); m_state.add_search_dir(input_filename.parent_path().string()); std::string text = trim_right(string_from_file(input_filename.string())); - katom_list ks = katomize_lines(line_split(text), input_filename); + katom_list ks = katomize_lines(line_split(text), input_filename.generic_string()); // ks = process_katoms( // ks, command_pathname, - ks, input_filename, + ks, input_filename.generic_string(), nonascii, literal, ignore, whitespace, klammers, eval, cond, read); katoms.insert(end, ks.begin(), ks.end()); } @@ -640,7 +640,7 @@ void Machine::load_klammerset_files(const Klammerset& klammerset, katom_iter ins std::vector filenames {}; for (const auto& required : klammerset.m_requires) { std::string path = is_klammerset_symbol(required) - ? resolve_klammerset_symbol(required, base_dir, klammerset.m_loc).string() + ? resolve_klammerset_symbol(required, base_dir, klammerset.m_loc).generic_string() : required; // ALREADY LOADED? Decided BEFORE the file is opened. The guard used // to sit in Klammerset_registry::add, which only runs once the file has @@ -682,11 +682,11 @@ void Machine::load_klammerset_files(const Klammerset& klammerset, katom_iter ins klammerset.m_loc); } pathname = fs::canonical(pathname); - loaded_files.push_back(pathname.string()); + loaded_files.push_back(pathname.generic_string()); m_state.add_search_dir(pathname.parent_path().string()); std::string text = trim_right(string_from_file(pathname.string())); - katom_list ks = katomize_lines(line_split(text), pathname); - process_katoms(ks, pathname); + katom_list ks = katomize_lines(line_split(text), pathname.generic_string()); + process_katoms(ks, pathname.generic_string()); loaded.insert(loaded.end(), ks.begin(), ks.end()); } m_katoms.insert(insert_at, loaded.begin(), loaded.end()); diff --git a/mac/state.cpp b/mac/state.cpp index 9932b27..ab29f83 100644 --- a/mac/state.cpp +++ b/mac/state.cpp @@ -357,7 +357,9 @@ std::string State::python_code() m_search_dirs.begin(), m_search_dirs.end()); for (const auto& d : python_dirs) { if (directory_has_python_file(d)) { - ss << "sys.path.append('" << d << "')\n"; + // A Python string literal, not the raw text: a Windows directory + // holds backslashes, and C:\Users reads as a \U unicode escape. + ss << "sys.path.append(" << pyformat_string({d}) << ")\n"; } } if (!m_frames.empty()) { diff --git a/mac/util.cpp b/mac/util.cpp index 5b47c85..ce894e0 100644 --- a/mac/util.cpp +++ b/mac/util.cpp @@ -1,3 +1,8 @@ +#include +#include +#include "error.h" +#include "file.h" +#include "locator.h" #include #include #include @@ -621,11 +626,51 @@ std::string freplace(const std::string& src, const std::regex& pattern, return result; } +// Shell_command -- see util.h. On Windows the script goes to a file under +// the temp directory (mkstemp, so concurrent runs do not collide), and the +// line is """ sh "