Skip to content

Makefile and build overhaul - encore - #547

Closed
dlmiles wants to merge 63 commits into
RTimothyEdwards:masterfrom
dlmiles:master-upstream-20260722-makefile-encore
Closed

dlmiles wants to merge 63 commits into
RTimothyEdwards:masterfrom
dlmiles:master-upstream-20260722-makefile-encore

Conversation

@dlmiles

@dlmiles dlmiles commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Makefile and build system overhaul - encore

The clankers have been busy. The clankers have turned my local attempts in these areas into a
more comprehensive solution in line with the original intention/goals they were moving towards.


CHANGE LOG NOTES:

  • Previously the documentation advised make database/database.h to seed the main compile. This
    has not been technically required since the last build-system overhaul, and the label has changed
    to make prepare so as to remove the hard dependency on the location of a specific file and use a
    more generic target unrelated to file existence. This is still optional for most/all builds, and
    for in-tree builds make database/database.h continues to work, but downstream builders should
    update their scripts.

  • INSTALL_MacOS.md notes a new optional package 'coreutils' for 'gdate' (GNU date). Some effort is
    made to be compatible with the Mac BSD-based 'date', but it is recommended to have 'gdate' on
    $PATH if you are packaging for third parties.

  • CAD_ROOT / TCL_DIR change, see above - not really expecting this to affect anyone.


configure - is now relocatable (out-of-tree builds).

cd magic; mkdir build-tmp; cd build-tmp; ../configure; make; ./run_magic.sh; make install DESTDIR=/tmp/stage

This now all works, this is probably the biggest blocker to magic being easily adopted by a modern
distribution as a regular package as the magic project predates autoconf and didn't get all the new
autoconf features as they arrived and out-of-tree building is the default where the sources are
treated read-only and the outputs end up in a $builddir.

in-tree building still continues to work

building multiple trees from the same source in different builddir using different configure
options and such should all now work with incremental build benefits.

We are still on autoconf 2.69 that is expected to be reviewed next time to look towards 2.71 / 2.72
even if we do not make use of new features from these, because they have become the new Linux
distro baseline.


Per-module Makefile.in migration - this is the mechanism that makes the out-of-tree build work.

Each module's Makefile is now a template (Makefile.in) that configure turns into a generated
Makefile in the $builddir via @srcdir@ substitution, rather than every subdir sharing one Makefile
that computed paths at make-time. config.status creates the build subdir with its Makefile, so
recursion is native (make -C

) and VPATH points back at the read-only source. In-tree the
build top and source top are the same directory, so nothing changes for that case.


A series of scripts run_*.sh are created in the $builddir and are designed to allow magic to be
run directly from the builddir without a make install:

run_magic.sh - GUI magic (Cairo/X11)
run_magicnull.sh - batch / no-GUI (-dnull)
run_magic_gdb.sh - magic under gdb
run_magic_valgrind.sh - magic under valgrind

On first run each one self-stages a $builddir/CAD_DIR directory (a tree of symlinks into the build
and source trees, no copies) and exports CAD_ROOT so magic finds its runtime files there. The gdb
and valgrind wrappers accept a "--" separator so you can pass options to the tool on the left and to
magic on the right.


CAD_ROOT / TCL_DIR - Due to making the "run from $builddir" feature work (without needing to
install), the TCL_DIR hardwired default is now constructed from $CAD_ROOT/magic/tcl at run time
when the CAD_ROOT environment variable is set and non-empty; otherwise the fallback to the hardwired
compile-time default still occurs. (CAD_ROOT is the runtime environment variable magic already
uses to relocate its sys files; CAD_DIR is the compile-time default and the name of the staged
directory.) If there is a downstream issue maybe we can add a $TCL_DIR override, but it would be
interesting to understand better the non-standard install layouts being used there if so; plus you
can always drop a symlink under $CAD_ROOT/magic/tcl. In the future the TCL_DIR compile-time
constant could perhaps be removed entirely.


The old legacy Depend mechanism has been removed. The SED processing that went with it may have
been to address a legacy compiler/toolchain matter that no longer exists today.

A more modern .deps/.d fragment is built per source file and transparently maintained by the
toolchain (compiler -MMD).

The depend system is only advisory anyway - it is not required to function to build everything, it
is only useful for those developing magic and wanting incremental build speeds. As a result there
is no longer a serialization barrier forcing dependencies to be built before compilation can start.


Parallelism - some work was done on this in the past to remove the worst offenders from the
compile/link phases. Now the build should be entirely parallelized so your make -j$(nproc) should
see full top-down rebuild benefits.

ccache is supported by default, if found at configure time; --disable-ccache exists to have
configure ignore it. configure --enable-ccache-prefix-map also exists to help scenarios where
independent build trees want to share compiler output. Since compile time is below 10s maybe this
is performance overkill at this time.


Reproducible build - the new configure option --disable-magic-builddate will remove the
MAGIC_BUILDDATE from the project, making the final executables/DSO more compatible with reproducible
build goals some package maintainers may be trying to achieve (due to recent supply-chain security
concerns). This means given the same environment/distro/toolchain and build options the output
executables/DSO can be compared against another system doing the same, to allow verification of
toolchain/supply-chain compromise. If there are other concerns found in this area please raise
issues.

If you are a developer maybe you also want to consider --disable-magic-builddate in your configure
since it removes the requirement to recompile buildinfo.o to update MAGIC_BUILDDATE every time you
re-link to test. (The build-info defines were isolated into a single small translation unit,
utils/buildinfo.c, which is what makes this - and ccache stability - possible.)

SOURCE_DATE_EPOCH is also honored (configure detects GNU vs BSD date); not fully tested but should
assist in the reproducible build space.


Bundled readline has been removed. The bundled copy was GNU readline 4.3 (over 23 years old;
doesn't really have any CVE security concern) but all platforms magic builds against can provide
their own more recent version, including SVR4/Solaris, the oldest tested against. This was done as
a deliberate three-patch series (require system readline, then remove the Makefile wiring, then
delete the readline/ subdirectory). When building non-Tcl/Tk the default now expects readline to be
present at configure time or it will fail with a clear message; --disable-readline still exists to
build without it, which is useful in slim/headless build modes.


WASM / Emscripten - the WebAssembly build was brought in line with the same out-of-tree principles
and unbroken against the current emsdk:

  • each variant now builds out-of-source (npm/build.sh runs configure from a dedicated build dir,
    the source tree is read-only);
  • --embed-file inputs are split by which tree they live in (generated tech from the build tree,
    verbatim glyphs/colour maps from the source tree) so out-of-source init works;
  • INCOMING_MODULE_JS_API is pinned to emscripten's full default list plus wasmBinary, which fixes
    a hard "wasmBinary not included" abort seen on emsdk 6.x;
  • magic is forced onto TCL's select()-based notifier because Emscripten's epoll is a non-working
    stub;
  • the disposable out-of-source WASM build directories are gitignored.

LaTeX documentation - this was hidden away; it has now been made part of the default build output,
with configure auto-detecting the necessary programs and advising what you are missing. (The CI /
AppImage packaging was updated to install texlive so the documents actually regenerate and ship.)

dlmiles and others added 30 commits July 24, 2026 09:27
The top-level ./configure wrapper did `cd scripts`, which is resolved
against the caller's current directory. Running it from anywhere other
than the source top (e.g. `mkdir build; cd build; ../configure`) failed
immediately with:

    ../configure: line 12: cd: scripts: No such file or directory

Derive the script's own location via `dirname "$0"` and cd into
"${basedir}/scripts" so the wrapper works regardless of CWD.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Enable `mkdir build; cd build; ../configure` to run entirely in the build
directory, with generated config landing there instead of in the source
tree.  This is the autoconf-layer groundwork for a full VPATH build; the
make layer (defs.mak/rules.mak VPATH support, per-dir orchestration) is
still to come, so an out-of-tree `make` is not yet expected to work.

Changes:

* configure (wrapper): run the generated config.status in the *current*
  directory with --srcdir pointing back at the source tree (instead of
  `cd scripts`), so outputs land in the build dir.  Uses an absolute
  basedir so any build-dir location works, not just a subdir of srcdir.

* scripts/configure.in:
  - AC_CONFIG_AUX_DIR(.) -> AC_CONFIG_AUX_DIR(scripts).  Once srcdir
    resolves to the true top (scripts' parent, where rules.mak lives),
    autoconf searches for install-sh/config.sub/... relative to srcdir;
    "." pointed at the wrong place and out-of-tree configure died with
    "cannot find install-sh".
  - Name the .in templates explicitly
    (defs.mak:scripts/defs.mak.in, scripts/makedbh:scripts/makedbh.in)
    so config.status finds them from a separate build dir.  defs.mak is
    written to the build top; makedbh under build/scripts/ to match
    ${SCRIPTS}/makedbh.
  - Drop the trailing `cp defs.mak ..`; defs.mak is now generated in the
    build top directly, and from a build dir the copy would clobber the
    source tree's defs.mak.

* scripts/configure: regenerated with autoconf 2.69.

Verified: out-of-tree `../configure` completes rc=0, writes defs.mak /
scripts/makedbh / config.status into the build dir, records
srcdir=<source top>, and leaves the source tree's tracked files and
generated defs.mak/makedbh untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
With the autoconf layer relocatable, teach the shared make
files to read sources from the source tree and write objects into the
build tree.  The ~26 "standard" and "near-standard" module Makefiles now
build out-of-tree with no per-module edits.

scripts/defs.mak.in:
* Add MAGICSRC = @abs_top_srcdir@ (absolute source top) and derive each
  module's srcdir/VPATH from CURDIR relative to the build top:
      BUILD_TOP      := $(abspath $(MAGICDIR))
      MODULE_SUBPATH := $(subst $(BUILD_TOP),,$(CURDIR))
      srcdir         := $(MAGICSRC)$(MODULE_SUBPATH)
      VPATH          := $(srcdir)
  (MAGICDIR keeps meaning the *build* top; in-tree the trees coincide.)
* CPPFLAGS: add -I${MAGICSRC} (source top, for "<module>/<hdr>.h") and
  -I${srcdir} (same-dir headers), keeping -I${MAGICDIR} for the generated
  database/database.h in the build top.
* MAGIC_VERSION/REVISION read ${MAGICSRC}/VERSION; MAGIC_COMMIT uses
  `git -C ${MAGICSRC}` so it works from a build dir outside the work-tree.

rules.mak:
* Compile rule: `-c $*.c` -> `-c $< -o $@` so the source is found via
  VPATH in srcdir and the object is written to the build cwd.
* Depend and tags rules: prefix sources with $(srcdir)/ (they are passed
  to the compiler/ctags directly, which do not honor VPATH).

scripts/configure.in: AC_CONFIG_FILES([rules.mak:rules.mak]) copies the
VPATH-aware rules.mak into the build top, so a single ${MAGICDIR}
resolves both `include`s in every subdir Makefile.  Regenerated configure.

Known limitation (v1): the Depend post-processing sed strips
absolute-path headers, so with an absolute srcdir most header deps are
dropped; out-of-tree incremental header-change rebuilds are weaker.
Depend is already "optimistic/optional"; relativizing is a follow-up.

Verified out-of-tree: `tiles` and `drc` (the latter #includes the
generated database/database.h) build to completion; all objects land in
the build tree; the source tree stays clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make a plain `make` in a build directory build every module into the
build tree.  The top Makefile becomes a configure-generated Makefile.in,
and each recursion drives a build subdirectory (created on demand) using
the module Makefile from the source tree.

Makefile -> Makefile.in (config.status generates Makefile in the build
top via AC_CONFIG_FILES([Makefile:Makefile.in])):
* Add MAGICSRC = @abs_top_srcdir@; read VERSION and re-run configure from
  ${MAGICSRC}.
* Every descent `(cd $$dir && ${MAKE} <goal>)` and `${MAKE} -C $(dir $@)
  <goal>` becomes:
      mkdir -p <builddir> && ${MAKE} -C <builddir> \
              -f ${MAGICSRC}/<dir>/Makefile <goal>
  so the build subdir need not pre-exist and the source Makefile is used.
  Applied to modules/libs/depend pattern rules and the
  tcllibrary/mains/techs/install/clean loops.
* database/database.h: generate from ${MAGICSRC}/.../database.h.in into
  the build tree (mkdir -p database), via the generated build-tree
  ${MAGICDIR}/scripts/makedbh.
* install-dirs/install-tcl-dirs use ${SCRIPTS}/mkdirs (now source-tree);
  clean skips absent build subdirs; distclean/dist/tags updated for the
  split source/build layout.

scripts/defs.mak.in:
* SCRIPTS now points at the source tree (${MAGICSRC}/scripts) for the
  static helpers (mkdirs, printmans, ...); the one generated helper,
  makedbh, is referenced directly at ${MAGICDIR}/scripts (build tree).
* Guard VPATH so it is set only inside module subdirectories, never at
  the build top -- a top-level VPATH into the source tree could let a
  stale source database/database.h shadow the build-tree copy.

scripts/configure.in: AC_CONFIG_FILES([Makefile:Makefile.in]); regenerated
scripts/configure.

Verified out-of-tree: `../configure && make modules && make libs` builds
all 33 module libraries (lib*.o and lib*.a) plus the generated
database/database.h into the build tree, with no errors and the source
tree left clean.  A full `make` (link the magic program, build techs)
still needs the later outlier fixes (magic/, scmos/, ...).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fix the remaining non-standard Makefiles so a full out-of-tree
`../configure && make && make install` builds and installs a complete
magic TCL tree, sources read from the source tree, all outputs in the
build tree.

Shared linker/preprocessor paths (scripts/configure.in, regenerated
scripts/configure):
* --version-script=${MAGICDIR}/magic/symbol.map -> ${MAGICSRC}/... so
  every shared-library link (tclmagic.so, ...) finds the source map.
* MCPP preproc.py path ${MAGICDIR}/scripts -> ${MAGICSRC}/scripts.

Programs (built via tcllibrary, PROGRAMS = magic tcltk):
* magic/Makefile: proto.magicrc, magicWasm.o and the bitmaps/.initrc/
  magicps.pro install copies use $< / $(srcdir) instead of bare names.
* tcltk/Makefile: magicexec/magicdnull compile $<; the sed'd launcher
  scripts (magic.tcl/.sh, ext2spice.sh, ext2sim.sh) read $<; VERSION dep
  -> ${MAGICSRC}; install-tcl copies each TCL file from the build dir if
  present else $(srcdir) (magic.tcl is generated, the rest are source).

Techs (scmos):
* scmos/Makefile: SC_PP template include -> $(srcdir)/extract_template;
  tech-file inputs (scmos.tech.in, *.tech.in) via $(srcdir)/$< ; recurse
  into cif_template with mkdir + -f ${MAGICSRC}/scmos/cif_template/Makefile;
  install copies from build-or-$(srcdir).
* scmos/cif_template/Makefile: MKDIR -> ${MAGICSRC}/scripts/mkdirs;
  SC_CPP gains -I$(srcdir); cpp reads $(srcdir)/cif{in,out}.c so the .gen
  includes resolve; objs/ generated in the build tree.

Data-file installs (source files copied from $(srcdir)):
* graphics/Makefile: glyphs, outline fonts, and the X11 helper build.
* windows/Makefile: glyphs and vector fonts.
* doc/Makefile: recurse into man/tutcells/html/latexfiles build-aware;
  doc/man, doc/tutcells install with $< ; doc/html tars $(srcdir);
  doc/latexfiles reads pre-built PostScript from ${MAGICSRC}/doc/psfiles.

Verified out-of-tree (mkdir _build; cd _build; ../configure): `make`
returns rc=0 (33 module libs, tclmagic.so, magicexec/magicdnull, scmos
tech files + cif_template objs, proto.magicrc), and
`make install DESTDIR=...` returns rc=0 installing 376 files
(bin/magic, tcl/tclmagic.so, sys/ glyphs+fonts+techs+dstyles), with the
source tree left completely clean.

Not exercised by the default TCL build/install and therefore not
converted here: the standalone .so/program variants of
lef/plot/router/ext2spice/ext2sim (their module libs do build), the
disabled oa module (-I. -> -I$(srcdir) still needed if enabled), the
WASM path, and doc/latexfiles PostScript *regeneration* (which still
writes into the source psfiles dir).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The out-of-tree conversion turned the top-level Makefile into a
configure-generated file (from Makefile.in), and config.status now runs
in the build directory (the source top for an in-tree build).  Ignore
both at the top level only (anchored) so the tracked per-module
Makefiles are unaffected.  config.log is already ignored.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend the out-of-tree build to the outliers deferred earlier, and
audit install sourcing.  All are read-source / write-build now.

Tcl auto-load shared libraries (built via each module's `tcl-main`;
not part of the default TCL build, but now correct when invoked):
* lef/plot/router/ext2spice/ext2sim: the hand-written object rules
  (tcllef.o, tclplot.o, tclroute.o, spicewrap.o, spicehier.o, simwrap.o,
  ext2sim_main.o) compiled a bare-name source from the cwd; switch to
  `-c $< -o $@` so the source is found via VPATH and the object lands in
  the build tree.  ext2sim_main.o also swaps the hard-coded -I${MAGICDIR}
  for ${CPPFLAGS} (build + source include paths).  ext2spice spice2sim
  install copies $< (source script).  Sibling ${MAGICDIR}/<mod>/lib*.o
  refs already resolve into the build tree (MAGICDIR = build top).

oa (disabled by default; OpenAccess):
* CXX_INCLUDES -I. -> -I$(srcdir) -I${MAGICDIR} -I${MAGICSRC} so the C++
  sources, generated database/database.h, and cross-module headers all
  resolve out-of-tree; .deps and objects were already build-local.

doc/latexfiles PostScript regeneration:
* Split PS_SRCDIR (pre-built PS shipped in the source tree, read-only)
  from PS_GENDIR (= psfiles in the build tree).  `all` / the .tex.ps
  rule now regenerate into PS_GENDIR (mkdir -p; latex reads $(srcdir)
  via TEXINPUTS; dvips writes the build tree) instead of writing back
  into the source psfiles dir.  install takes each .ps from PS_GENDIR if
  regenerated there, else the pre-built PS_SRCDIR copy.

Verified out-of-tree (make modules; then per-module tcl-main):
* magiclef.so / tclplot.so / tclroute.so / exttospice.so / exttosim.so
  all build into the build tree (rc=0); oa.o builds into the build tree;
  source tree stays clean.
* install-tcl copies each .so from the build tree.
* Install-location audit on a full `make install DESTDIR=...`: generated
  data (scmos.tech, .magicrc, magic.tcl, tclmagic.so) matches the build
  tree; verbatim data (glyphs, fonts, magicps.pro, man pages, doc PS)
  matches the source tree.
* DESTDIR: two distinct prefixes install 376 files each, independent and
  fully contained (nothing written outside DESTDIR).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
magic/Makefile ran the WASM post-processor via ${MAGICDIR} (the build
top).  Out-of-tree that resolves to build/toolchains/... which does not
exist — the script is a source file.  Use ${MAGICSRC}.  In-tree the two
are identical, so behaviour is unchanged there.

(Static edit; not runtime-verified — emscripten is not available here.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Emscripten link embedded data files via magic-relative paths
(../scmos, ../windows/...), which only resolve when the build dir is the
source dir.  Split them by which tree the data lives in:

* generated scmos tech files -> ${MAGICDIR}/scmos (the build tree, where
  `make techs` writes them);
* verbatim window glyphs      -> ${MAGICSRC}/windows/... (the source tree).

Out-of-tree, ../windows/windows7.glyphs pointed at build/windows/ (which
holds only objects) and the link would fail to embed it.  In-tree the two
trees coincide, so the resulting .wasm is unchanged.

(Static edit; not runtime-verified — emscripten is not available here.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
npm/build.sh built in-tree: it cd'd into the source root, ran
`./configure` there, appended the WASM defs.mak to the source-tree
defs.mak, and copied magic.js/.wasm out of the source magic/ dir.

Run each variant in a dedicated out-of-source build dir instead
(build-wasm-<variant>, overridable via WASM_BUILD_DIR):

* configure is invoked as "$REPO_ROOT/configure" from $build_dir, so
  config.status and all generated files land under $build_dir; the
  source tree is only read (VPATH).
* the WASM defs.mak is appended to $build_dir/defs.mak.
* artifacts are copied from $build_dir/magic/.
* a fresh `rm -rf $build_dir` per run replaces the old in-tree
  `make distclean` (which also removes the need to touch the source
  tree, and sidesteps the tracked-rules.mak distclean hazard entirely).

CRLF stripping now targets the source-tree configure inputs explicitly.

Note: build-wasm-<variant> dirs are not gitignored (left per task scope);
they are disposable and recreated each run.

(Static edit; not runtime-verified — emscripten is not available here.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
npm/build.sh now builds each WASM variant out-of-source in
build-wasm-<variant> under the repo root (fixed names build-wasm-tcl /
build-wasm-notcl).  They are disposable (recreated each run) and must not
be tracked.  Confirmed the relocated build dirs receive all output while
the source module dirs stay clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
TCL 9.1's configure defines NOTIFIER_EPOLL whenever <sys/epoll.h> is
present (unix/configure.ac:342).  Emscripten's sysroot ships that header
and the epoll_* symbols, so TCL picked the epoll notifier — but epoll
does not work under WASM at runtime and the event loop hangs.

Hide the header/functions from TCL's configure via autoconf cache vars
(ac_cv_header_sys_epoll_h=no + epoll_create/epoll_create1=no) so the
select()-based notifier is selected instead.

Verified with emsdk 6.0.3: NOTIFIER_EPOLL is absent from DEFS, the
tclEpollNotfy.o/tclKqueueNotfy.o objects are empty stubs (333 B) and
tclSelectNotfy.o is the live notifier (~9.9 KB); the tcl WASM variant
links (magic.js + magic.wasm) via npm/build.sh out-of-source.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When latex and dvips are available, the default no-arg "make" now
regenerates the PostScript documentation from its .tex sources into the
build tree; otherwise the pre-built PostScript shipped in the source tree
is used as-is at install time.  Documentation is always available either
way -- installing latex simply refreshes it from source.

configure (scripts/configure.in, regenerated scripts/configure):
* AC_PATH_PROG for latex + dvips; DOCS_TARGET = "docs" only when both are
  found; AC_SUBST(LATEX/DVIPS/DOCS_TARGET).
* Configuration summary prints "LaTeX docs: yes/no".

scripts/defs.mak.in: export LATEX/DVIPS.

Makefile.in:
* all: ... $(DOCS_TARGET) -- pulls in "docs" only when latex was found.
* docs: best-effort -- `make -k` + a warning on failure so a document
  that will not typeset (or a missing texlive package) never fails the
  build; install falls back to the pre-built PostScript for those.

doc/latexfiles/Makefile:
* use ${LATEX}/${DVIPS} from defs.mak.
* the .tex include figures as ../psfigures/*.ps; point the build-tree
  ../psfigures at ${MAGICSRC}/doc/psfigures (no-op in-tree) so latex and
  dvips find them while outputs stay in the build tree.

CI: main.yml and main-aarch64.yml install
texlive-latex-base/-recommended + texlive-fonts-recommended so the Linux
builds exercise doc regeneration (best-effort, never fatal).

README.Tcl: document latex/dvips as an optional prerequisite for docs.

.gitignore: ignore in-tree doc build artifacts (doc/latexfiles/psfiles/,
*.dvi/*.aux/*.log); the pre-built doc/psfiles/ stays tracked.

Verified out-of-tree with TeX Live 2024: configure reports "LaTeX docs:
yes"; `make` regenerates 27/28 documents into the build tree (maint1.tex
has a genuine LaTeX error and falls back), stays exit 0; `make install`
installs the 27 regenerated PostScript from the build tree and maint1
from the source pre-built copy; the source tree is not modified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The AppImage builders already shipped the pre-built PostScript
documentation (each Dockerfile runs "make install", and INSTALL_CAD_DIRS
includes doc/), but they did not install latex/dvips, so the docs were
never regenerated from source.

Install texlive (latex + dvips + recommended fonts) before the magic
build in all four EL variants (yum on EL7, dnf on EL8/9/10), so configure
reports "LaTeX docs: yes" and the default "make" regenerates the docs
into the build before "make install" ships them.

The install is non-fatal (|| echo): if a texlive package is unavailable
on a given EL release it falls back to the pre-built PostScript, so the
release image build can never be broken by documentation, and docs are
shipped either way.

NOTE: not build-tested here (no Docker/EL toolchain available).  The
package names (texlive-latex, texlive-dvips,
texlive-collection-fontsrecommended) should be validated on each EL
image; the non-fatal guard protects the tag-triggered release builds in
the meantime.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two long-standing breakages in `make -C ext2sim main`:

* finds.c included <string.h> *after* utils/magic.h.  magic.h defines the
  legacy SysV bcopy/bzero/bcmp compat macros in terms of memcpy/memset/
  memcmp; with a modern glibc that then declares those functions, the
  macro expansion collides ("conflicting types for 'memcpy'").  Include
  the C library headers before magic.h so the real prototypes win.

* the `main` target listed `sim2simp`, which has no source file and no
  rule (and `ext2sim_main` was already commented out as deprecated), so
  the target always failed.  Reduce `main` to the one real artifact,
  `finds`.

Verified: `make -C ext2sim -f .../ext2sim/Makefile main` now builds
`finds` cleanly (rc=0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The non-Tcl ("standard") configuration links the bundled GNU readline,
and that path was broken two ways:

* headers: readline's own headers use <readline/*.h> includes, so the
  readline source's parent must be on the include path.  Nothing added
  it, so every module including readline (commands, textio, ...) failed
  with "readline/rlstdc.h: No such file".  Add -I${MAGICSRC}/readline to
  CPPFLAGS for the bundled build only (@MAKE_READLINE@ = 1).

* library: readline/Makefile ran "../readline/configure" from the
  build-readline dir -- a source-relative path that, out-of-tree, points
  into the (empty) build tree.  Use ${MAGICSRC}/readline/readline/
  configure so the out-of-source readline build finds its sources.

Verified: `mkdir b; cd b; ../configure --without-tcl && make` now
completes (rc=0) and produces the native magic binary; previously it
died at commands/CmdFI.o (headers) then at the readline library build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend the Linux CI so consumers exercise more of what is buildable:

* In the Tcl job, after the default build, also build the per-module Tcl
  auto-load shared libraries (lef/plot/router/ext2spice/ext2sim
  tcl-main -> magiclef.so, tclplot.so, tclroute.so, exttospice.so,
  exttosim.so).  These are real products the default target never builds,
  so building them in CI keeps them from bit-rotting.

* Add a standard_build_linux job for the non-Tcl configuration
  (./configure --without-tcl): the native magic binary + bundled GNU
  readline -- code paths the Tcl build does not cover, and which only
  build now that the bundled-readline out-of-tree fixes landed.

NB: the standard job's apt package set is not validated on the runner;
adjust if a library is not found.  Docs remain best-effort in both jobs.

Not changed here: whether the five auto-load .so should also be shipped
by the default "make install" (they currently are not) -- left as a
separate decision.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Previously the generated database/database.h was written to the build
top and exposed with -I${MAGICDIR}.  Out-of-tree that placed the raw
build directory on the compiler's include search path.

Introduce ${GENINC}, the single include dir for generated headers:
  * in-tree  (build top == source top): ${MAGICDIR}, so the header stays
    at $(builddir)/database/database.h and -I${GENINC} is exactly the old
    -I${MAGICDIR} -- behaviour is unchanged.
  * relocated (out-of-tree): ${MAGICDIR}/include, so the header is written
    to $(builddir)/include/database/database.h and only that clean
    include/ dir is on the search path; the raw build top never is.

* scripts/defs.mak.in: derive GENINC from BUILD_TOP vs MAGICSRC; add
  DATABASE_H = ${GENINC}/database/database.h; CPPFLAGS uses -I${GENINC}.
* Makefile.in: the makedbh rule targets ${DATABASE_H} (mkdir its dir);
  every prereq and the distclean use ${DATABASE_H}.
* database/Makefile: the fallback delegate targets ${DATABASE_H}.

Verified: relocated `make modules` builds all 33 module libraries with
the header at build/include/database/database.h and no raw build-top on
any -I line; in-tree the header stays at database/database.h and the
-I set is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… path

The generated header's path now depends on in-tree vs relocated builds
(database/database.h vs include/database/database.h), so instructions
that named it directly ("make database/database.h") are brittle.

Add a stable, path-independent top-level target:

    .PHONY: prepare
    prepare: ${DATABASE_H}

Being .PHONY the name is always valid, but its prerequisite is the real
file target ${DATABASE_H}, so the header is still regenerated only when
out of date -- same result, no exposed path.

Convert every "make database/database.h" in the docs, appimage
Dockerfiles, and CI workflows to "make prepare".

Verified: relocated `make prepare` generates include/database/database.h;
re-running is a no-op; touching database.h.in triggers regeneration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Emit an autoconf config header carrying every autoconf define -- at least
all the -D options that appear on the compiler command lines, plus
anything else configure knows -- available to the tree as
    #include <magic/autoconf/config.h>

It is built directly from confdefs.h just before AC_OUTPUT (which removes
that file), NOT via AC_CONFIG_HEADERS.  This is deliberate:

  * AC_CONFIG_HEADERS would rewrite @defs@ to -DHAVE_CONFIG_H and move the
    defines into the header, changing every compiler command line and
    breaking the build until sources #include it.  Building from confdefs.h
    leaves @defs@ (and the whole build) exactly as-is, so the header is a
    pure additional artifact with no consumers yet.
  * confdefs.h already holds every AC_DEFINE, including the many that have
    no autoheader template (CALMA_MODULE, HAVE_READLINE, ...), which
    AC_CONFIG_HEADERS/autoheader would have dropped.

Location mirrors the generated database.h (defs.mak GENINC): magic/autoconf/
config.h at the build top when in-tree (build dir == source dir), else
include/magic/autoconf/config.h so the clean -I include/ rule finds it and
the raw build dir stays off the search path.  The directory is created on
demand and the header is rewritten on every ./configure.  No defs.mak.in
change is needed -- the existing -I${GENINC} already resolves it.

.gitignore: ignore the in-tree magic/autoconf/config.h (out-of-tree it is
under the build dir and already ignored).

Verified: out-of-tree writes include/magic/autoconf/config.h, in-tree
writes magic/autoconf/config.h; both contain the command-line defines
(SIZEOF_VOID_P, CALMA_MODULE, HAVE_*, ...); #include <magic/autoconf/
config.h> resolves via -I${GENINC} from a subdir; @defs@ is unchanged
(-DHAVE_CONFIG_H absent) and modules still build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Enabler for the Makefile -> Makefile.in migration.  No module is
converted yet; behaviour is identical.

* scripts/defs.mak.in: dual-mode.  A module converted to Makefile.in will
  set `srcdir` (and VPATH) from @srcdir@ before including defs.mak; only
  derive them (the $(abspath)/$(patsubst) srcdir computation) when it
  did not -- `ifeq ($(origin srcdir),undefined)`.  BUILD_TOP (needed by
  GENINC) is computed either way.

* Makefile.in: route every subdir recursion through a single helper,
      $(call submake,<dir>,<goal>)
  which uses the generated build Makefile if config.status made one there
  (a converted Makefile.in) and otherwise falls back to the source
  Makefile with -f.  Today nothing is converted, so out-of-tree always
  takes the -f path and in-tree uses the (identical) static Makefile
  natively.  clean/ keeps its own no-mkdir variant.  readline/ is
  intentionally never converted and always takes -f.

* .gitignore: document the migration policy -- each converted module adds
  its generated "<dir>/Makefile" here in the same commit so an in-tree
  build can never commit a generated Makefile.  (None converted yet.)

Verified in-tree and out-of-tree: `configure && make && make install`
each produce 376 files with the header in the right place (database/
database.h in-tree, include/database/database.h relocated), 27/28 docs,
source tree clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First module of the Makefile.in migration (proof).  Pure rename: the
Makefile content is unchanged, so config.status just copies it and the
module still builds via the defs.mak srcdir *derivation* (dual mode from
phase 1) -- the @srcdir@ substitution is a separate follow-up.

* git mv tiles/Makefile -> tiles/Makefile.in
* configure.in: AC_CONFIG_FILES([tiles/Makefile:tiles/Makefile.in]);
  regenerated configure.  config.status now creates build/tiles/Makefile,
  so $(submake) recurses into tiles natively (make -C tiles), no -f.
* .gitignore: ignore the generated tiles/Makefile (in-tree it lands in the
  source tree) -- added in this same commit so it can never be committed.

Verified out-of-tree: config.status writes build/tiles/Makefile,
make -C tiles module builds libtiles.o (rc=0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switch tiles from the defs.mak srcdir *derivation* to configure-provided
substitution:
  srcdir   = @srcdir@
  VPATH    = @srcdir@
  MAGICDIR = @top_builddir@
defs.mak's dual-mode guard now sees `srcdir` already set (origin=file) and
skips the $(abspath)/$(patsubst) srcdir computation for this module.

Verified: config.status writes srcdir=<src>/tiles into build/tiles/Makefile;
make -C tiles module compiles from that srcdir (libtiles.o, rc=0) with the
derivation bypassed.  This is the template the remaining modules follow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… unit)

Second batch of the Makefile.in migration: the remaining standard modules
  bplane calma cif cmwind dbwind debug drc extflat extract garouter gcr
  grouter irouter mzrouter netmenu plow resis select sim textio wiring
Pure renames -- content unchanged, so each still builds via the defs.mak
srcdir derivation (dual mode); the @srcdir@ substitution follows.

* git mv <dir>/Makefile -> <dir>/Makefile.in (21 modules)
* configure.in: AC_CONFIG_FILES for each; regenerated configure.  Their
  build subdirs + Makefiles are now created by config.status, so
  $(submake) recurses natively (make -C <dir>), no -f.
* .gitignore: ignore each generated <dir>/Makefile, in this same commit.

Verified out-of-tree: config.status writes 22 module Makefiles (these 21 +
tiles); `make modules` builds all 33 module libraries (rc=0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switch the 21 standard modules from the defs.mak srcdir derivation to
configure-provided substitution (same template as tiles):
  srcdir   = @srcdir@
  VPATH    = @srcdir@
  MAGICDIR = @top_builddir@
defs.mak's dual-mode guard sees `srcdir` already set (origin=file) and
skips the srcdir derivation for these modules.

Verified out-of-tree: config.status writes srcdir=<src>/<mod> into each
build/<mod>/Makefile; `make modules libs` builds all 33 module libraries
(lib*.o + lib*.a, rc=0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ename unit)

Third batch of the Makefile.in migration: near-standard modules
(utils windows commands database) and the program/.so outliers
(lef plot router ext2spice ext2sim extcheck net2ir graphics oa).
Pure renames -- content unchanged, so each still builds via the defs.mak
srcdir derivation; the @srcdir@ substitution follows.

* git mv <dir>/Makefile -> <dir>/Makefile.in (13 modules)
* configure.in: AC_CONFIG_FILES for each; regenerated configure.
* .gitignore: ignore each generated <dir>/Makefile, same commit.

These modules carry sibling-object refs (${MAGICDIR}/<mod>/lib*.o) and
custom rules; those keep working unchanged (${MAGICDIR} = the build top,
$(srcdir) from the derivation).  readline/ remains on the -f path.

Verified out-of-tree: config.status writes 35 module Makefiles (22 + 13);
`make modules libs` builds all 33 module libraries (rc=0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switch the 13 near-standard/outlier modules to configure-provided
substitution (same template as the standard modules):
  srcdir   = @srcdir@
  VPATH    = @srcdir@
  MAGICDIR = @top_builddir@
Their sibling-object refs (${MAGICDIR}/<mod>/lib*.o) and custom rules are
unchanged: ${MAGICDIR} = @top_builddir@ (the build top) so the sibling
objects resolve into the build tree, and $(srcdir) now comes from
@srcdir@ instead of the derivation.

Verified out-of-tree: `make modules libs` builds all 33 module libraries;
the five Tcl auto-load libraries (magiclef.so, tclplot.so, tclroute.so,
exttospice.so, exttosim.so) link (sibling refs resolve to the build top).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… (rename unit)

Fourth batch of the Makefile.in migration: the top-level complex outliers
  magic tcltk scmos doc lisp
Pure renames -- content unchanged, so each still builds via the defs.mak
srcdir derivation; the @srcdir@ substitution follows.

Their nested children (scmos/cif_template and doc/{man,tutcells,html,
latexfiles}) are intentionally NOT converted here: scmos and doc recurse
into them with `-f ${MAGICSRC}/.../Makefile`, which keeps working only
while those children remain static Makefiles.  They convert in a later
step together with the parent-recursion updates.

* git mv <dir>/Makefile -> <dir>/Makefile.in (5 modules)
* configure.in: AC_CONFIG_FILES for each; regenerated configure.
* .gitignore: ignore each generated <dir>/Makefile, same commit.

Verified out-of-tree: full `make` rc=0 -- tclmagic.so, scmos techs (scmos
recurses into the static cif_template via -f, 8 objs), 27/28 docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switch the 5 top-level outlier modules to configure-provided substitution
(same template as the rest):
  srcdir   = @srcdir@
  VPATH    = @srcdir@
  MAGICDIR = @top_builddir@
Their custom rules and ${MAGICDIR}/${MAGICSRC} references keep working:
${MAGICDIR} = @top_builddir@ (build top), $(srcdir) now from @srcdir@.
scmos/doc still recurse into their (static) children via -f.

Verified out-of-tree: full `make` rc=0 -- magic gets srcdir=<src>/magic,
tclmagic.so + scmos techs + 27/28 docs build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Convert the last nested children to Makefile.in and switch their parents
from the -f fallback to native recursion (they now have generated build
Makefiles).  Rename and rewire are one unit: a pure rename would break the
parent's -f path to the (now-.in) child.

Converted (5): scmos/cif_template, doc/man, doc/tutcells, doc/html,
doc/latexfiles.  Pure renames -- still build via the srcdir derivation;
@srcdir@ substitution follows.

Rewired to native `make -C <child>` (config.status now creates the child
build dirs + Makefiles):
* scmos/Makefile.in: cif_template build + clean.
* doc/Makefile.in: install/install-tcl over man/tutcells/html/latexfiles.
* Makefile.in: the docs: target -> `make -k -C doc/latexfiles all`.
The `mkdir -p` and `-f ${MAGICSRC}/.../Makefile` are dropped in each.

* configure.in: AC_CONFIG_FILES for the 5 children; regenerated configure.
* .gitignore: ignore each generated child Makefile, same commit.

Verified out-of-tree: full `make && make install` rc=0, 376 files --
scmos recurses natively into cif_template (8 objs), docs regenerate via
native doc/latexfiles (27/28), man pages install via native doc/man.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
dlmiles and others added 23 commits July 24, 2026 12:02
The WASM link embeds ${MAGICDIR}/scmos (the build tree) at the VFS sys dir,
which out of source holds only the *generated* tech files.  A technology's
"styles" section (styletype mos) also needs the display styles and colour
maps -- scmos ${FILES}: mos.*.dstyle / mos.*.cmap -- which are *source*
files absent from the build tree.  So magic_wasm_init failed:

  Couldn't open color map file "mos.7bit.std.cmap"
  ... minimum.tech ... [styles] ... Cannot load technology "minimum"

(In-tree this never showed because build == source.  It was also masked
until now behind the wasmBinary/INCOMING_MODULE_JS_API abort.)

Embed each display file from the source tree into the same VFS dir via
$(wildcard), which matches only files that exist -- so a stale/typo'd name
in ${FILES} (e.g. the non-existent mos.7bit.mraster_dstyle) can't break the
link.

Verified on emsdk 6.0.3, both variants: smoke tests now pass 4/4 (notcl)
and 5/5 (tcl).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The tcl smoke test writes generated artifacts to npm/examples/output-tcl/,
mirroring the already-ignored notcl npm/examples/output/.  Ignore it too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ndly)

MAGIC_VERSION / MAGIC_REVISION / MAGIC_COMMIT / MAGIC_BUILDDATE were on the
global DFLAGS, so every object's compile command line carried them.  Because
the command line is part of ccache/sccache's hash key and MAGIC_BUILDDATE
changes every second, that meant a cache miss on every unit on every build;
it also smeared the baked-in date/commit across "whenever each file last
recompiled".

Move the four values into a single translation unit:
  * utils/magic_buildinfo.h -- extern MagicVersion/MagicRevision/MagicCommit/
    MagicCompileTime (MagicCommit is new; the other three moved here from
    utils/magic.h, which now includes this header).
  * utils/buildinfo.c -- the ONLY unit compiled with the version defines
    (target-specific `buildinfo.o: DFLAGS += ${DFLAGS_MAGICVERSION}` in
    utils/Makefile.in); it defines the four globals.  ${DFLAGS_MAGICVERSION}
    is removed from the global DFLAGS/DFLAGS_NOSTUB.

Consumers updated to read the runtime symbol instead of the compile-time
macro:
  * magicTop.c / tclmagic.c no longer define the globals (the MAGIC_WRAPPER
    duplicate-symbol guard is gone -- buildinfo.o owns them for every variant,
    including WASM, which links both mains).
  * Tcl_PkgProvide/PkgRequire in router/ext2spice/lef/ext2sim/plot/tclmagic
    now pass MagicVersion.
  * extflat: EFVersion (a static-initialized copy of MAGIC_VERSION) is dropped;
    EFread.c compares the .ext version against MagicVersion directly -- the same
    value ExtCell.c already *writes* into .ext files, so read and write are now
    consistent.

Verified: only buildinfo.o carries -DMAGIC_* (hash/DBio/windCmdSZ carry none);
Tcl (375 files) and --without-tcl builds rc=0, version/commit/date embedded in
tclmagic.so, magicTop.o no longer defines MagicVersion; in-tree source clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
MAGIC_BUILDDATE now derives from SOURCE_DATE_EPOCH (UTC) when it is set,
falling back to the current local time otherwise.  This makes the date baked
into buildinfo.o reproducible for the appimage/npm tarballs (cf. npm/pack.sh,
which already normalizes mtimes).

Also add `2>/dev/null` to the MAGIC_COMMIT git call so a source tarball or a
non-work-tree ${MAGICSRC} yields an empty commit rather than a git error.

Only buildinfo.o consumes these (see the prior commit), so the shell still
runs at defs.mak parse time but affects just that one unit's define.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lds)

New configure option --disable-magic-builddate omits the build date from the
binary.  It is wired via an AC_SUBST'd MAGIC_BUILDDATE_DEFS (not AC_DEFINE), so
it reaches only buildinfo.o rather than @defs@ / every command line:

  * enabled (default): buildinfo.o gets -DMAGIC_BUILDDATE="<date>" and defs.mak
    computes the date (SOURCE_DATE_EPOCH-aware);
  * disabled: buildinfo.o gets -DMAGIC_NO_BUILDDATE instead, defs.mak skips the
    date computation entirely (no parse-time `date` spawn), and buildinfo.c
    reports an empty MagicCompileTime.

This gives a fully reproducible build whose one volatile input (the date) is
gone; what remains in buildinfo.o is commit/version/revision, which do not
change second-to-second -- the intended pairing with ccache for day-to-day
development.

Verified both configs: default bakes the date + commit; --disable-magic-builddate
compiles buildinfo.o with -DMAGIC_NO_BUILDDATE (no date), commit still present.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When ccache is on PATH, prefix CC/CXX with it so every compile goes through
ccache -- no Makefile change, since rules.mak already runs `${CC} ... -c` and
defs.mak substitutes @cc@/@cxx@.  Autodetected on by default; --disable-ccache
opts out.

Placed after the AC_PROG_C* checks so configure's own feature tests use the
plain compiler and only the build picks up the prefix.  Pairs with the
build-info isolation (utils/buildinfo.c): with the volatile MAGIC_BUILDDATE on
only that one unit, a full rebuild hits the cache for every other unit.

Measured (default Tcl build, all objects deleted then rebuilt): 323/324 hits
with the date enabled (only buildinfo.o misses), 324/324 with
--disable-magic-builddate.

Verified: default configure -> CC="ccache gcc", CXX="ccache g++";
--disable-ccache -> CC="gcc".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The out-of-tree build puts absolute source-root paths on the command line
(-I${MAGICSRC}, -I${srcdir}, the source file itself), which are part of
ccache's key -- so same-tree rebuilds hit (proven) but different checkouts /
build dirs do not share cache.

New opt-in configure --enable-ccache-prefix-map adds
  -ffile-prefix-map=${MAGICSRC}=.
to CFLAGS (requires ccache; the flag is probed for GCC-8+/clang support and
skipped with a warning otherwise).  This relativizes the source paths embedded
in the compiled output (__FILE__, debug info) so a cached object is valid
regardless of which tree produced it.

Off by default: it makes debug paths relative, so gdb then needs
`set substitute-path . <srcdir>`.  It is only for cross-tree *correctness* and
must be paired with CCACHE_BASEDIR=<build top> in the environment for cross-tree
*hits* (that half normalizes the command-line paths in the hash; this half keeps
the cached output tree-neutral).  Same-tree caching needs neither.

Verified: default/--disable-ccache carry no -ffile-prefix-map;
--enable-ccache-prefix-map (probe "yes") adds it, and with --disable-ccache it
warns and is ignored.  Full build rc=0 (375 files); the installed tclmagic.so
contains zero absolute /work/magic paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…TALL

Add three configure options to the INSTALL option list:
  * --disable-magic-builddate -- omit the build date for reproducible builds
    (byte-identical objects for a given commit) and full ccache effectiveness.
  * --disable-ccache -- opt out of the auto-detected ccache.
  * --enable-ccache-prefix-map -- relativize embedded source paths for
    cross-tree ccache sharing (off by default; note the debugger and
    CCACHE_BASEDIR implications).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… machinery

Replace the monolithic per-directory `Depend` file -- generated by a separate,
serial `make depend` pass (one `gcc -MM` over all sources) and post-processed by
sed -- with automake-style per-file dependency fragments produced as a side
effect of each compile.

rules.mak's %.o rule now adds, when the compiler supports it,
  -MMD -MP -MF .deps/<stem>.d
so every object records the (non-system) headers it used.  Those fragments are
`-include`d best-effort: absent on a first build, present and correct after.
Benefits:
  * self-maintaining -- no explicit depend step; deps refresh on every compile;
  * parallel -- generated during the (already parallel) compile, no barrier;
  * -MMD already drops system headers (what the sed did) and -MP tolerates a
    removed/renamed header, so the sed post-processing is gone;
  * fixes out-of-tree incremental header tracking: with an absolute srcdir the
    old sed stripped the now-absolute *local* header paths too, silently losing
    them; the .d files keep them.

configure probes `-MMD -MP` (AUTODEP_FLAGS; empty and best-effort-skipped if the
compiler cannot).  clean/distclean remove .deps (rm -r); .gitignore replaces the
obsolete */Depend with .deps/.  `make depend` is kept as a no-op for callers that
still invoke it (e.g. the WASM build script).

Verified out-of-tree (--disable-ccache): 324 .d files, 0 Depend files, a header
touch recompiles its dependents, no-op rebuild is idle, clean clears .deps.  (The
top-level depend phase still runs here as a no-op; removed next.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
With per-file dependencies now generated during compilation (previous commit),
the serial `depend` phase and its barrier are obsolete.  Drop SUBDIRS_DEPEND and
the `<dir>/Depend: ${DATABASE_H}` rule, and replace the `module@/lib@/tech@ %:
depend` prerequisite with `: ${DATABASE_H}` -- the only real ordering constraint
is the generated header, not a whole dependency-scanning pass.  Modules therefore
start compiling in parallel immediately instead of waiting for every subdir's
deps to be scanned first.

`make depend` is kept as a top-level no-op (the WASM build script still calls
`emmake make depend`).

Verified out-of-tree serial and -j12 and in-tree -j12: rc=0, 375 files, serial
and -j installs byte-identical, zero Depend files, source tree clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
From an independent review of the branch:

* Makefile.in: `tcltk` is in both INSTALL_CAD_DIRS and PROGRAMS (Tcl build),
  so install@tcltk / install-tcl@tcltk were defined twice -> "given more than
  once" warnings on every command.  Wrap both install lists in $(sort ...) to
  dedup (as CLEAN_TARGETS already does).  Also fix a stale comment (module@ now
  waits for ${DATABASE_H}, not a `depend` phase).

* toolchains/emscripten/defs.mak: the scmos display-file embed globbed
  `mos.*.dstyle`, which misses mos.7bit.mraster_dstyle (an underscore, not a
  dot) -- so the mono-raster style was not embedded in WASM, and the comment
  wrongly called that file non-existent.  Glob `mos.*dstyle` to cover both.

* npm/build.sh: sed_strip_cr wrote the file back unconditionally, bumping the
  mtimes of configure and every scripts/ file on each WASM build (contradicting
  the "source never mutated" guarantee and able to force a coexisting native
  rebuild).  Only write back when a CR was actually stripped.

* graphics/Makefile.in: remove the dead DEPSRCS block left behind by retiring
  the Depend machinery (it also referenced an undefined ${THREE_D_SRCS}).

* scripts/defs.mak.in: clarify that only the SOURCE_DATE_EPOCH branch is UTC.

Verified: 0 duplicate-target warnings, wildcard now lists mos.7bit.mraster_dstyle,
full Tcl build+install rc=0 (375 files), graphics lib builds, build.sh syntax OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The SOURCE_DATE_EPOCH -> date conversion previously relied on a runtime
`date -u -d @epoch 2>/dev/null || date -u -r EPOCH` fallback.  That is
unreliable on macOS/*BSD: BSD `date -d` reads -d as a DST flag and silently
prints the wrong time (exit 0), so the `-r` fallback is never reached.

Decide at configure time instead: prefer coreutils `gdate` (GNU date, e.g.
installed on macOS), else a native `date` that really is GNU (verified by the
epoch-0 output containing 1970), else BSD `date -r`.  configure substitutes the
chosen invocation as @SOURCE_DATE_CMD@ and defs.mak appends the epoch.

Verified on Linux (no gdate -> GNU `date -u -d @`): a fixed SOURCE_DATE_EPOCH
yields a byte-identical buildinfo.o across builds; the no-epoch path still
stamps the current local time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ipping

npm/build.sh stripped CRs from configure and every scripts/ file before each
WASM build.  Its purpose was purely to survive a Windows checkout with
core.autocrlf=true (which rewrites LF->CRLF, and bash/make then reject CRLF) --
not reproducible packaging.  No tracked build input is CRLF today
(`git ls-files --eol` shows only the incidental appimage/rsc/magic.svg), so it
never fired on a normal checkout, and it mutated the source tree to do it.

Add .gitattributes `eol=lf` for configure, scripts/**, *.sh, *.mak, *.mak.in
and Makefile.in.  Attributes override core.autocrlf, so git always checks these
out as LF regardless of platform -- the authoritative fix -- and the source tree
is never rewritten by the build.  Remove sed_strip_cr and its call sites.

Verified: git check-attr reports eol=lf for configure/scripts/*/*.sh/rules.mak/
Makefile.in; build.sh parses cleanly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
MAGIC_VERSION/REVISION/COMMIT/BUILDDATE used `?=` with `$(shell ...)`, which
makes them recursively-expanded.  Because they are exported, make re-evaluated
(re-spawned git/date/cat) every time it built a child's environment -- so a
no-op recursive `make` spawned git and date ~624 times each.

Guard each with `ifndef` and assign with `:=` (simply-expanded) so the value is
computed once at the top and exported as a plain string; sub-makes inherit it
and the ifndef skips recomputation.  This was the last "fold in" item from the
buildinfo/ccache plan.

Verified: a no-op `make` now spawns git and date once each (was 624); a full
clean build spawns 1 git / 2 date / 10 cat; the commit is still embedded
correctly and builds are reproducible.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The build's SOURCE_DATE_EPOCH date handling auto-detects gdate/GNU date and
falls back to native BSD `date -r`, so coreutils is not a prerequisite (unlike
gnu-sed).  Add an optional note so the reproducible-build audience knows it is
available but not needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…atch 1)

configure already preferred the system readline (AC_CHECK_LIB → -lreadline) and
only fell back to the bundled readline/ copy.  Remove that fallback: a non-Tcl
interactive build now requires the *system* GNU readline -- both its header
(AC_CHECK_HEADER readline/readline.h, so configure fails cleanly instead of deep
in compilation) and the library -- and if neither Tcl nor readline is available
configure errors with a clear explanation of the three ways forward (build with
Tcl, install libreadline-dev, or --disable-readline).

Also drop the now-meaningless --enable-readline-bundled, and substitute
MAKE_READLINE=0 unconditionally so the Makefiles' bundled-readline path goes
inert.  readline/ and all Makefiles are left untouched here (cleaned up and
removed in the following patches).

--disable-readline stays as the escape hatch: it just tells configure to ignore
the system readline and build the existing no-line-editing variant.

Verified: Tcl build rc=0 (readline off, 375 files, readline/ not built); non-Tcl
--disable-readline builds (serial); non-Tcl default with no readline dev package
now errors with the guidance message.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
main@ depends on both `modules` and `libs`, so `module@X` (`make -C X module`)
and `lib@X` (`make -C X lib`) could run in the same directory concurrently under
-j and race on X's shared objects -- each %.o recipe does `rm $*.o; cc -c`, so
one make would delete/rebuild an object while the other was linking it, e.g.
`ld: cannot find touchtypes.o` while linking libutils.o.  It was rare (timing
dependent) but real; it surfaced on a --without-tcl --disable-readline -j12 build.

Make `lib@%` depend on its own `module@%`: module@X builds the objects, lib@X
then only archives them, so the two never touch X's *.o at the same time.
Cross-directory parallelism is unchanged (lib@X waits only on module@X, not the
whole modules phase).

Verified: 4/4 clean --without-tcl -j12 builds pass (previously intermittently
failed); Tcl -j12 still builds 375 files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
With the bundled fallback gone (Patch 1) the readline/ special-casing is dead
machinery.  Remove it:

  * Makefile.in: $(submake) drops the `= readline` `-f`/mkdir branch and is now
    plain `$(MAKE) -C <dir> <goal>`; the clean loop drops its matching branch;
    `readline` is removed from BUNDLED_MODULES.
  * scripts/defs.mak.in: remove the `ifeq (@MAKE_READLINE@,1) -I${MAGICSRC}/
    readline` block (system readline headers are on the standard search path) and
    the MAKE_READLINE variable; tidy the srcdir-fallback comment (only the
    top-level Makefile uses it now).
  * scripts/configure.in: drop AC_SUBST(MAKE_READLINE); regenerate configure.

The system readline link flags (rl_libs = -lreadline via READLINE_LIBS) are
unchanged.  readline/ itself is deleted in the next patch.

Verified: no unsubstituted @MAKE_READLINE@; Tcl -j12 builds+installs 375 files
with readline/ not built; --without-tcl --disable-readline -j12 builds (x2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Delete readline/ (the vendored GNU readline 4.3, ~104 files / 2 MB) now that
configure requires the system readline (Patch 1) and the Makefiles no longer
wire the bundled build (Patch 2).  Sweep the remaining references:

  * commands/Makefile.in: drop the `module: ${MAGICDIR}/readline/readline` rule
    and the readline-create-symlinks delegation (bundled-only; commands now uses
    the default `module: libcommands.o`).
  * commands/CmdFI.c, textio/txMain.c, textio/txInput.c: the readline includes
    had a `#ifdef HAVE_READLINE <system> #else "readline/readline/*.h" (bundled)`
    split.  HAVE_READLINE/NEED_READLINE are used nowhere else and USE_READLINE now
    always implies the system library, so collapse to the plain
    `#include <readline/readline.h>` / `<history.h>`.
  * CI: main.yml's non-Tcl job installs libreadline-dev (was relying on bundled);
    canary-matrix drops the now-invalid no_tk_tcl_brl / --enable-readline-bundled
    variant.
  * .gitignore: drop the "readline/ intentionally not converted" note.

Verified: Tcl -j12 builds+installs 375 files (commands builds, no readline-dir
references); --without-tcl --disable-readline -j12 builds (x2); --without-tcl with
no readline dev package still errors with the guidance message.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…_DIR

magicexec.c, magicdnull.c and tclmagic.c hardwired the compile-time TCL_DIR
(the install location) when locating magic.tcl (tcl_rcFileName) and the Tcl
auto_path.  So even with CAD_ROOT set -- the documented relocation knob, already
honored by the sys-file search and by tcltk/magic.sh's
TCL_MAG_DIR=${CAD_ROOT}/magic/tcl -- the C launchers still looked in the baked
install path, which is why magic could not source its startup from a relocated
install or a build tree.

Add tcltk/tcldir.h with a MagicTclDir() helper: $CAD_ROOT/magic/tcl when CAD_ROOT
is set and non-empty, else the compile-time TCL_DIR.  Use it in all three sites.
This makes the C side consistent with the shell wrappers and with how magic.tcl
itself already derives its own directory (from `info script`).

Verified: default (no CAD_ROOT) resolves to TCL_DIR as before (build rc=0); with
CAD_ROOT pointed at a staged build tree, `magicdnull -dnull` sources magic.tcl
from there and runs (tech load scmos -> 13 planes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Four configure-generated launch scripts let you run magic straight from the
build directory without `make install`:

  * run_magic.sh          -- GUI magic (Cairo/X11)
  * run_magicnull.sh      -- batch / no-GUI (-dnull)
  * run_magic_gdb.sh      -- magic under gdb
  * run_magic_valgrind.sh -- magic under valgrind

magic resolves its runtime files through a single $CAD_ROOT root
($CAD_ROOT/magic/{sys,tcl}), but in the build tree they are scattered across
build/ (generated tech, .magicrc, magic.tcl, tclmagic, execs) and source/
(colormaps, dstyles, glyphs, fonts, tcl scripts).  On first run each launcher
self-stages ${builddir}/CAD_DIR as a symlink tree in that layout (no copies,
always current) via the shared scripts/magic_run_common.sh, exports
CAD_ROOT=${builddir}/CAD_DIR, and execs the right binary.  This pairs with the
preceding commit that made the C launchers honor CAD_ROOT.

magic_stage() is idempotent and re-runs on every launch.  Most staged entries
are files, but the bitmaps entry links a *directory*, so it uses `ln -sfn`
(--no-dereference; also accepted by BSD ln): a plain `ln -sf` would, on a second
run, dereference the existing symlink-to-directory and drop the new link *inside*
the target -- a magic/bitmaps/bitmaps self-loop in the source tree that trips up
find/tar/cp -r.

Verified: ./run_magicnull.sh self-stages (30 sys + 24 tcl symlinks) and runs --
`tech load scmos` reports 13 planes -- straight from the build dir.  gitignore
covers the generated scripts and the CAD_DIR tree.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…he env

Expand each run_magic*.sh header to state its purpose, usage, and the full set
of environment overrides.  Initialize every knob with ${VAR:-default} so a value
inherited from the environment always wins:

  - CAD_ROOT is now overridable (was hardwired to $builddir/CAD_DIR); point it at
    a shared/installed tree to skip staging.
  - WISH/TCLSH default to the wish/tclsh configure detected for this build's
    Tcl/Tk (@WISH_EXE@/@TCLSH_EXE@, matching --with-tk/--with-tcl), so a build
    against a non-standard Tcl/Tk automatically uses the correct absolute paths.
    MAGIC_WISH (what tkcon.tcl uses for the Tk console) defaults from WISH.

Add magic_check_env(): on source, sanity-check each value (directory / regular
file / executable, as appropriate) and print a non-fatal stderr warning naming
the offending variable and whether it came from the environment or the baked
default -- to make a bad override easy to diagnose.  Empty optional values
(e.g. no tclsh configured) and a not-yet-created CAD_ROOT are not flagged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The gdb and valgrind launchers previously passed every argument straight to
magic, leaving no way to give the tool its own options.  Add a "--" split
(shared magic_split_args helper): arguments before "--" go to gdb/valgrind,
arguments after go to magic; with no "--", all arguments go to magic (the
common case).  Examples:

  ./run_magic_gdb.sh -tui -ex run -- -dnull foo.mag
  ./run_magic_valgrind.sh --tool=callgrind -- foo.mag

Also make the tool binary and its default options overridable from the
environment: GDB/GDB_OPTS and VALGRIND/VALGRIND_OPTS (valgrind defaults to
--leak-check=full --error-exitcode=0), and warn if the tool is not on PATH.

Smoke-tested against a live X display: gdb runs magic to a clean exit and
valgrind produces a Memcheck report; option routing verified (a bogus flag
after "--" is rejected by magic, not gdb), both headless (-dnull) and GUI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@RTimothyEdwards

Copy link
Copy Markdown
Owner

I would point out that the LaTeX documentation was more or less hidden away on purpose, because I migrated all the documentation to HTML and the original .tex sources are considerably out of date (another job for "the clankers"?).

@RTimothyEdwards RTimothyEdwards left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very useful set of updates. My only concern is the mess of outdated information that is likely to end up in the compiled .tex documentation, which has not been kept up to date with the HTML documentation. However, making sure all forms of documentation (including the man pages) are all up to date and agreeing with each other can be done in a later commit.

I especially like having the "Depend" mechanism removed, as it was making the compile time slower than it used to be.

@RTimothyEdwards

Copy link
Copy Markdown
Owner

pulled and merged to opencircuitdesign.com

@dlmiles

dlmiles commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator Author

@RTimothyEdwards So what is being said, the docs/html/** information supersedes the docs/latexfiles/** in expected information quality ? and it is only the subdir latexfiles that could contain out-of-date information ? all the other subdirs (that are not regenerated) are also up-to-date ?

@RTimothyEdwards

Copy link
Copy Markdown
Owner

@dlmiles : Well, I did say that the files in man/ are also out of date. Stuff in psfiles/ really shouldn't be there at all because it's all generated from the latexfiles/. The psfigures/ are just figures for the documentation and tutcells/ are example layouts for the tutorials. Those are fine although the tutorials themselves need updating.

A long time ago I took the maint2.ps compiled from the LaTeX source and converted it to HTML, but it's on the website at opencircuitdesign.com and not in the repository (where it belongs). Currently, my nightly script updates a clone of the repository on my web server, and the web pages for magic link directly to the HTML documentation there, so I only need to update documentation in the repository source. I need to do the same thing with maint2.html. maint2.html on the website is up to date but not very well organized.

tskaar pushed a commit to tskaar/magic that referenced this pull request Jul 28, 2026
committed by Darryl Miles in PR RTimothyEdwards#546 and RTimothyEdwards#547 (changes to enable
in-tree building, although there are numerous other changes here,
including removing the clunky Depend method and removing the copy
of readline source in the code base).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants