From e0c6872cf40896c7be36b11dcc744620f10adf1d Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Mon, 2 Sep 2019 13:46:59 +0900 Subject: Initial commit --- macros/latex/contrib/l3build/CHANGELOG.md | 346 +++ macros/latex/contrib/l3build/CONTRIBUTING.md | 52 + macros/latex/contrib/l3build/README.md | 36 + macros/latex/contrib/l3build/l3build-arguments.lua | 301 +++ macros/latex/contrib/l3build/l3build-aux.lua | 126 ++ macros/latex/contrib/l3build/l3build-check.lua | 990 ++++++++ macros/latex/contrib/l3build/l3build-clean.lua | 56 + macros/latex/contrib/l3build/l3build-ctan.lua | 159 ++ .../contrib/l3build/l3build-file-functions.lua | 444 ++++ macros/latex/contrib/l3build/l3build-help.lua | 86 + macros/latex/contrib/l3build/l3build-install.lua | 268 +++ .../contrib/l3build/l3build-manifest-setup.lua | 364 +++ macros/latex/contrib/l3build/l3build-manifest.lua | 280 +++ macros/latex/contrib/l3build/l3build-stdmain.lua | 185 ++ macros/latex/contrib/l3build/l3build-tagging.lua | 84 + .../latex/contrib/l3build/l3build-typesetting.lua | 233 ++ macros/latex/contrib/l3build/l3build-unpack.lua | 102 + macros/latex/contrib/l3build/l3build-upload.lua | 350 +++ macros/latex/contrib/l3build/l3build-variables.lua | 205 ++ macros/latex/contrib/l3build/l3build.1 | 67 + macros/latex/contrib/l3build/l3build.dtx | 2369 ++++++++++++++++++++ macros/latex/contrib/l3build/l3build.ins | 56 + macros/latex/contrib/l3build/l3build.lua | 189 ++ macros/latex/contrib/l3build/l3build.pdf | Bin 0 -> 703605 bytes 24 files changed, 7348 insertions(+) create mode 100644 macros/latex/contrib/l3build/CHANGELOG.md create mode 100644 macros/latex/contrib/l3build/CONTRIBUTING.md create mode 100644 macros/latex/contrib/l3build/README.md create mode 100644 macros/latex/contrib/l3build/l3build-arguments.lua create mode 100644 macros/latex/contrib/l3build/l3build-aux.lua create mode 100644 macros/latex/contrib/l3build/l3build-check.lua create mode 100644 macros/latex/contrib/l3build/l3build-clean.lua create mode 100644 macros/latex/contrib/l3build/l3build-ctan.lua create mode 100644 macros/latex/contrib/l3build/l3build-file-functions.lua create mode 100644 macros/latex/contrib/l3build/l3build-help.lua create mode 100644 macros/latex/contrib/l3build/l3build-install.lua create mode 100644 macros/latex/contrib/l3build/l3build-manifest-setup.lua create mode 100644 macros/latex/contrib/l3build/l3build-manifest.lua create mode 100644 macros/latex/contrib/l3build/l3build-stdmain.lua create mode 100644 macros/latex/contrib/l3build/l3build-tagging.lua create mode 100644 macros/latex/contrib/l3build/l3build-typesetting.lua create mode 100644 macros/latex/contrib/l3build/l3build-unpack.lua create mode 100644 macros/latex/contrib/l3build/l3build-upload.lua create mode 100644 macros/latex/contrib/l3build/l3build-variables.lua create mode 100644 macros/latex/contrib/l3build/l3build.1 create mode 100644 macros/latex/contrib/l3build/l3build.dtx create mode 100644 macros/latex/contrib/l3build/l3build.ins create mode 100644 macros/latex/contrib/l3build/l3build.lua create mode 100644 macros/latex/contrib/l3build/l3build.pdf (limited to 'macros/latex/contrib/l3build') diff --git a/macros/latex/contrib/l3build/CHANGELOG.md b/macros/latex/contrib/l3build/CHANGELOG.md new file mode 100644 index 0000000000..b7d20890a6 --- /dev/null +++ b/macros/latex/contrib/l3build/CHANGELOG.md @@ -0,0 +1,346 @@ +# Changelog +All notable changes to the `l3build` bundle since the start of 2018 +will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +this project uses date-based 'snapshot' version identifiers. + +## [Unreleased] + +## [2019-08-24] + +### Changed + +- Include `LUAINPUTS` when setting `TEXINPUTS` for `checksearch = false`, etc. + +### Fixed + +- Remove `.log` file before each check run: prevent inter-engine confusion + +## [2019-07-31] + +### Fixed + +- Interaction between secondary files in some tests + +## [2019-07-30] + +### Added + +- Support for non-standard file layouts via `tdslocations` table + +### Changed + +- Only write (x)dvipdfmx specials for XeTeX and (u)pTeX (see #94) + +## [2019-06-27] + +### Changed + +- Back out change for normalisation of LuaTeX v1.10 stack information + (timing issue: will reintroduce later) + +### Fixed + +- Correct test for LuaTeX (see #93) + +## [2019-06-26] + +### Added + +- Support for HarfTeX (see #92) + +### Changed + +- Support for normalisation of LuaTeX v1.10 stack information + (may require new `.tlg` files) + +### Fixed + +- Support for spaces in paths when typesetting (see #91) + +## [2019-06-18] + +### Added + +- Switch `--show-log-on-error` for use with `--halt-on-error`. Results in the `.log` file + being show in full on the console to aid in non-interactive debugging. + +### Changed + +- Moved LuaTeX-specific font cache normalisation + +## [2019-02-10] + +### Fixed + +- Handling of ASCII engines +- Execution of all tests by `ctan` target (see #85) + +## [2019-02-06] + +### Added + +- Table-based control of binary/format combinations: `specialformats` + (see #84) +- Switch `--debug` for chasing problems with the `upload` target + +### Changed + +- The `uploadconfig.update` field by default now automatically detects whether it + needs to be `true` or `false` +- The `tag` target now allows no tag name to allow for setting this programmatically + within a `build.lua` script +- Better support for multiple LuaTeX-like engines + +### Fixed + +- Uploading via Windows should now work + +## [2018-12-23] + +### Added + +- Switch `--email` for providing upload email address +- Switch `-F|--file` for providing upload announcement from file +- Switch `-m|--message` for providing upload announcement from command line + +### Changed + +- Enable `--dry-run` option for `upload` target +- Enable tag/version to be passed as optional argument to `upload` target + +### Fixed + +- Packaging of some team-specific files +- Handling of upload data containing newlines (Windows only) + +## [2018-12-18] + +### Changed + +- Add new `upload` target which uses `curl` with the CTAN API to send a package + for release (see #1) + +## [2018-11-08] + +### Changed + +- Strip leading spaces from file loading/page number lines (see #78) + +### Fixed + +- Print test failures correctly per-config (see #77) + +## [2018-10-30] + +### Fixed + +- Substitution of spaces in Unix paths (see #76) + +## [2018-10-25] + +### Added + +- `\SHOWFILE` command + +### Changed + +- Better support for multiple LuaTeX engines (see #75) + +## Fixed + +- Support for spaces in directory names (fixes #76) +- Support relative directories as argument to `--texmfhome` + +## [2018-09-23] + +### Changed + +- Entries in `scriptfiles` are excluded from `installfiles` +- Use a per-config directory for running tests +- Enable use of local `texmf.cnf` file in tests and documentation +- New `ctanreadme` variable + +## [2018-09-21] + +### Changed + +- Normalise date lines to contain "...-..-.." rather than removing + (including normalising version data in such lines): note that + `.tlg` file updates may be required after this change +- Explicitly exclude `.fd` file lines + +## [2018-08-07] + +### Changed +- Suppress file name info in PDF output for included images, etc. + +### Fixed +- Issue with creation of CTAN releases for bundles + +## [2018-08-04] + +### Added +- `ctanzip` variable +- `--full` option + +### Changed +- Run PDF-based tests for all engines +- Tweaks to PDF-based normalisation: new `.tpf` files will be required +- The `--halt-on-error|-H` setting now applies to multiple configs + +### Fixed +- Testing using `.lve` files +- Tagging with new approach to top-level targets + +## [2018-08-02] + +### Added +- `CHANGELOG.md` +- `--dirty` option +- `includetests` and `excludetests` variables for controlling which tests + run +- `target_list` table to allow control of targets without redefining + `main()` + +### Changed +- PDF-based testing now uses 'digested' PDF file for comparison, + working from dedicated `.pvt` input files + +### Removed +- `--pdf|-p` command line switch + +## [2018-05-10] + +### Changed +- Revert appearance of date lines in `.tlg` files: + this is on balance problematic + +## [2018-05-06] + +### Added +- Variable `dynamicfiles` to be cleaned between each test run + +### Changed +- Normalise dates to placeholder "YYYY-MM-DD": may require `.tlg` updates + +### Fixed +- Include dot files in `tree()` (fixes #30) + +## [2018-03-26] + +### Changed +- Omit ISO date lines in `.tlg` files + +## [2018-03-24] + +### Changed +- Allow 'short cut' of check runs +- Support for upcoming LaTeX kernel release functions + +## [2018-03-10] + +### Changed +- Add `#!` line for POSIX users +- Set POSIX u+x on `l3build.lua` + +### Fixed +- Handling of script name with or without extension + +## [2018-03-09] + +### Fixed +- Pass through script name correctly with new set up + +## [2018-03-08] + +### Added +- Target `tag`, variable `tagfiles` and function `update_tag()` +- Variables `scriptfiles` and `scriptmanfiles` to support installation + of scripts + +### Changed +- `l3build` can now be run as a top-level script rather than using + `texlua build.lua ...` +- Normalisation of LuaTeX-derived `tlg` files, in preparation for + TeX Live 2018 + +### Deprecated +- Use of wrapper `build.lua` script to call `l3build`: the new + top-level script approach is preferred + +### Removed +- Target `setversion` and variable `versionfiles` + +## [2018-02-20] + +### Changed +- Allow for `checkopts` adding code/files + +### Fixed +- Creation of 'structured' CTAN releases +- Quote test names correctly + +## [2018-01-27] + +### Added +- Target `uninstall` +- Options `--first` and `--last` + +## Changed +- Normalisation for upcoming LuaTeX 1.07 release + +### Fixed +- Behaviour of check on Windows when using standard `fc` tool + +## [2018-01-10] + +## Added +- Target `manifest` for construction of file manifests automatically +- Variable `auxfiles` +- Option `--dry-run` for installation/cleaning +- Option `--texmfhome` to allow customisation of installation +- Option `--shuffle` to run tests in a random order + +### Changed +- Sort list of tests to avoid system-dependent ordering +- Split `l3build` into multiple files for improved maintenance + +### Fixed +- Issue with `recordstatus` + +### Removed +- Rationalise short option names: removed `-d`, `-E`, `-r` +- Target `cmdcheck`: specific to LaTeX3 kernel work + +[Unreleased]: https://github.com/latex3/l3build/compare/2019-08-24...HEAD +[2019-08-24]: https://github.com/latex3/l3build/compare/2019-07-31...2019-08-24 +[2019-07-31]: https://github.com/latex3/l3build/compare/2019-07-30...2019-07-31 +[2019-07-30]: https://github.com/latex3/l3build/compare/2019-06-27...2019-07-30 +[2019-06-27]: https://github.com/latex3/l3build/compare/2019-06-26...2019-06-27 +[2019-06-26]: https://github.com/latex3/l3build/compare/2019-06-18...2019-06-26 +[2019-06-18]: https://github.com/latex3/l3build/compare/2019-02-10...2019-06-18 +[2019-02-10]: https://github.com/latex3/l3build/compare/2019-02-06...2019-02-10 +[2019-02-06]: https://github.com/latex3/l3build/compare/2018-12-23...2019-02-06 +[2018-12-23]: https://github.com/latex3/l3build/compare/2018-12-18...2018-12-23 +[2018-12-18]: https://github.com/latex3/l3build/compare/2018-11-08...2018-12-18 +[2018-11-08]: https://github.com/latex3/l3build/compare/2018-10-30...2018-11-08 +[2018-10-30]: https://github.com/latex3/l3build/compare/2018-10-25...2018-10-30 +[2018-10-25]: https://github.com/latex3/l3build/compare/2018-09-26...2018-10-25 +[2018-09-26]: https://github.com/latex3/l3build/compare/2018-09-23...2018-09-26 +[2018-09-23]: https://github.com/latex3/l3build/compare/2018-09-21...2018-09-23 +[2018-09-21]: https://github.com/latex3/l3build/compare/2018-08-07...2018-09-21 +[2018-08-07]: https://github.com/latex3/l3build/compare/2018-08-04...2018-08-07 +[2018-08-04]: https://github.com/latex3/l3build/compare/2018-08-02...2018-08-04 +[2018-08-02]: https://github.com/latex3/l3build/compare/2018-05-10...2018-08-02 +[2018-05-10]: https://github.com/latex3/l3build/compare/2018-05-06...2018-05-10 +[2018-05-06]: https://github.com/latex3/l3build/compare/2018-03-26...2018-05-06 +[2018-03-26]: https://github.com/latex3/l3build/compare/2018-03-24...2018-03-26 +[2018-03-24]: https://github.com/latex3/l3build/compare/2018-03-10...2018-03-24 +[2018-03-10]: https://github.com/latex3/l3build/compare/2018-03-09...2018-03-10 +[2018-03-09]: https://github.com/latex3/l3build/compare/2018-03-08...2018-03-09 +[2018-03-08]: https://github.com/latex3/l3build/compare/2018-02-20...2018-03-08 +[2018-02-20]: https://github.com/latex3/l3build/compare/2018-01-27...2018-02-20 +[2018-01-27]: https://github.com/latex3/l3build/compare/2018-01-10...2018-01-27 +[2018-01-10]: https://github.com/latex3/l3build/compare/2017-12-12...2018-01-10 diff --git a/macros/latex/contrib/l3build/CONTRIBUTING.md b/macros/latex/contrib/l3build/CONTRIBUTING.md new file mode 100644 index 0000000000..a152299e83 --- /dev/null +++ b/macros/latex/contrib/l3build/CONTRIBUTING.md @@ -0,0 +1,52 @@ +Thanks for considering contributing to `l3build`: feedback, fixes and ideas are +all useful. Here, we ([The LaTeX3 Project](https://www.latex-project.org)) have +collected together a few pointers to help things along. + +## Bugs + +Please log bugs using the [issues](https://github.com/latex3/l3build/issues) +system on GitHub, and choose the 'bug' label. Handy information that you might +include, depending on the nature of the issue, includes + +- Your version of `l3build` (`texlua l3build.lua version`) +- Your TeX system details (for example 'TeX Live 2017') +- Your operating system +- The contents of your `build.lua` file +- An 'ASCII art' explanation of your directory layout + +## Feature requests + +Feature requests are welcome: log them in the same way as bugs and pick +the 'Enhancement' label. We welcome feature requests for the test set up, +the build process, _etc._ + +## Code contributions + +If you want to discuss a possible contribution before (or instead of) +making a pull request, drop a line to +[the team](mailto:latex-team@latex-project.org). + +There are a few things that might look non-standard to most Lua programmers, +which come about as `l3build`'s focus is testing and building LaTeX packages: + +- Our target Lua set up is `texlua` (part of LuaTeX), not standalone `lua` +- The main `l3build.lua` file is self-contained as this helps with + bootstrapping LaTeX: we are aiming to maintain a single file with no + `.lua` dependencies +- The primary documentation is aimed at the TeX world, so is in PDF format + and generated from `l3build.dtx`; documentation in the `.lua` file is + also welcome, but anything for general use does need to be in the `.dtx` +- As far as possible, everything is done within `l3build` itself or tools + directly available in a TeX system or as standard in the supported + systems (Windows, MacOS, Linux) +- The `l3build` interfaces should be platform-agnostic (though it may be + necessary of course to branch inside particular functions) + +If you are submitting a pull request, notice that + +- We use Travis-CI for (light) testing so add `[ci skip]` to documentation-only + commit messages +- We favour a single linear history so will rebase agreed pull requests on to + the `master` branch +- Where a commit fixes or closes an issue, please include this information + in the first line of the commit message diff --git a/macros/latex/contrib/l3build/README.md b/macros/latex/contrib/l3build/README.md new file mode 100644 index 0000000000..16abce04d9 --- /dev/null +++ b/macros/latex/contrib/l3build/README.md @@ -0,0 +1,36 @@ +l3build: a testing and building system for LaTeX3 +================================================= + +Release 2019-08-24 + +Overview +-------- + +The `l3build` module is designed to support the development of +high-quality LaTeX code by providing: +* A unit testing system +* Automated typesetting of code sources +* A reliable packaging system for CTAN releases + +The bundle consists of a Lua script to run the tasks and a +`.tex` file which provides the testing environment. These were +originally developed for supporting LaTeX3 development but +are designed such that they can be readily used by others. Full +documentation is provided. + +Issues +------ + +The issue tracker for LaTeX3 is currently located +[on GitHub](https://github.com/latex3/l3build/issues). + +Development team +---------------- + +The LaTeX kernel is developed by [The LaTeX3 Project](https://latex-project.org). + +----- + +

Copyright (C) 2014-2019 The LaTeX3 Project
+http://latex-project.org/
+All rights reserved.

diff --git a/macros/latex/contrib/l3build/l3build-arguments.lua b/macros/latex/contrib/l3build/l3build-arguments.lua new file mode 100644 index 0000000000..cbf68c6254 --- /dev/null +++ b/macros/latex/contrib/l3build/l3build-arguments.lua @@ -0,0 +1,301 @@ +--[[ + +File l3build-arguments.lua Copyright (C) 2018 The LaTeX3 Project + +It may be distributed and/or modified under the conditions of the +LaTeX Project Public License (LPPL), either version 1.3c of this +license or (at your option) any later version. The latest version +of this license is in the file + + http://www.latex-project.org/lppl.txt + +This file is part of the "l3build bundle" (The Work in LPPL) +and all files in that bundle must be distributed together. + +----------------------------------------------------------------------- + +The development version of the bundle can be found at + + https://github.com/latex3/l3build + +for those people who are interested. + +--]] + +local exit = os.exit +local stderr = io.stderr + +local find = string.find +local gmatch = string.gmatch +local match = string.match +local sub = string.sub + +local insert = table.insert + +-- Parse command line options + +option_list = + { + config = + { + desc = "Sets the config(s) used for running tests", + short = "c", + type = "table" + }, + date = + { + desc = "Sets the date to insert into sources", + type = "string" + }, + debug = + { + desc = "Runs target in debug mode (not supported by all targets)", + type = "boolean" + }, + dirty = + { + desc = "Skip cleaning up the test area", + type = "boolean" + }, + ["dry-run"] = + { + desc = "Dry run for install", + type = "boolean" + }, + email = + { + desc = "Email address of CTAN uploader", + type = "string" + }, + engine = + { + desc = "Sets the engine(s) to use for running test", + short = "e", + type = "table" + }, + epoch = + { + desc = "Sets the epoch for tests and typesetting", + type = "string" + }, + file = + { + desc = "Take the upload announcement from the given file", + short = "F", + type = "string" + }, + first = + { + desc = "Name of first test to run", + type = "string" + }, + force = + { + desc = "Force tests to run if engine is not set up", + short = "f", + type = "boolean" + }, + full = + { + desc = "Install all files", + type = "boolean" + }, + ["halt-on-error"] = + { + desc = "Stops running tests after the first failure", + short = "H", + type = "boolean" + }, + help = + { + short = "h", + type = "boolean" + }, + last = + { + desc = "Name of last test to run", + type = "string" + }, + message = + { + desc = "Text for upload announcement message", + short = "m", + type = "string" + }, + quiet = + { + desc = "Suppresses TeX output when unpacking", + short = "q", + type = "boolean" + }, + rerun = + { + desc = "Skip setup: simply rerun tests", + type = "boolean" + }, + ["show-log-on-error"] = + { + desc = "If 'halt-on-error' stops, show the full log of the failure", + type = "boolean" + }, + shuffle = + { + desc = "Shuffle order of tests", + type = "boolean" + }, + texmfhome = + { + desc = "Location of user texmf tree", + type = "string" + } + } + +-- This is done as a function (rather than do ... end) as it allows early +-- termination (break) +local function argparse() + local result = { } + local names = { } + local long_options = { } + local short_options = { } + -- Turn long/short options into two lookup tables + for k,v in pairs(option_list) do + if v["short"] then + short_options[v["short"]] = k + end + long_options[k] = k + end + local args = args + -- arg[1] is a special case: must be a command or "-h"/"--help" + -- Deal with this by assuming help and storing only apparently-valid + -- input + local a = arg[1] + result["target"] = "help" + if a then + -- No options are allowed in position 1, so filter those out + if a == "--version" then + result["target"] = "version" + elseif not match(a, "^%-") then + result["target"] = a + end + end + -- Stop here if help or version is required + if result["target"] == "help" or result["target"] == "version" then + return result + end + -- An auxiliary to grab all file names into a table + local function remainder(num) + local names = { } + for i = num, #arg do + insert(names, arg[i]) + end + return names + end + -- Examine all other arguments + -- Use a while loop rather than for as this makes it easier + -- to grab arg for optionals where appropriate + local i = 2 + while i <= #arg do + local a = arg[i] + -- Terminate search for options + if a == "--" then + names = remainder(i + 1) + break + end + -- Look for optionals + local opt + local optarg + local opts + -- Look for and option and get it into a variable + if match(a, "^%-") then + if match(a, "^%-%-") then + opts = long_options + local pos = find(a, "=", 1, true) + if pos then + opt = sub(a, 3, pos - 1) + optarg = sub(a, pos + 1) + else + opt = sub(a, 3) + end + else + opts = short_options + opt = sub(a, 2, 2) + -- Only set optarg if it is there + if #a > 2 then + optarg = sub(a, 3) + end + end + -- Now check that the option is valid and sort out the argument + -- if required + local optname = opts[opt] + if optname then + -- Tidy up arguments + if option_list[optname]["type"] == "boolean" then + if optarg then + local opt = "-" .. (match(a, "^%-%-") and "-" or "") .. opt + stderr:write("Value not allowed for option " .. opt .."\n") + return {"help"} + end + else + if not optarg then + optarg = arg[i + 1] + if not optarg then + stderr:write("Missing value for option " .. a .."\n") + return {"help"} + end + i = i + 1 + end + end + else + stderr:write("Unknown option " .. a .."\n") + return {"help"} + end + -- Store the result + if optarg then + if option_list[optname]["type"] == "string" then + result[optname] = optarg + else + local opts = result[optname] or { } + for hit in gmatch(optarg, "([^,%s]+)") do + insert(opts, hit) + end + result[optname] = opts + end + else + result[optname] = true + end + i = i + 1 + end + if not opt then + names = remainder(i) + break + end + end + if next(names) then + result["names"] = names + end + return result +end + +options = argparse() + +-- Sanity check +function check_engines() + if options["engine"] and not options["force"] then + -- Make a lookup table + local t = { } + for _, engine in pairs(checkengines) do + t[engine] = true + end + for _, engine in pairs(options["engine"]) do + if not t[engine] then + print("\n! Error: Engine \"" .. engine .. "\" not set up for testing!") + print("\n Valid values are:") + for _, engine in ipairs(checkengines) do + print(" - " .. engine) + end + print("") + exit(1) + end + end + end +end diff --git a/macros/latex/contrib/l3build/l3build-aux.lua b/macros/latex/contrib/l3build/l3build-aux.lua new file mode 100644 index 0000000000..2146d9a142 --- /dev/null +++ b/macros/latex/contrib/l3build/l3build-aux.lua @@ -0,0 +1,126 @@ +--[[ + +File l3build-aux.lua Copyright (C) 2018 The LaTeX3 Project + +It may be distributed and/or modified under the conditions of the +LaTeX Project Public License (LPPL), either version 1.3c of this +license or (at your option) any later version. The latest version +of this license is in the file + + http://www.latex-project.org/lppl.txt + +This file is part of the "l3build bundle" (The Work in LPPL) +and all files in that bundle must be distributed together. + +----------------------------------------------------------------------- + +The development version of the bundle can be found at + + https://github.com/latex3/l3build + +for those people who are interested. + +--]] + +local match = string.match + +local pairs = pairs +local print = print + +local lookup = kpse.lookup + +-- +-- Auxiliary functions which are used by more than one main function +-- + +function normalise_epoch() + -- If given as an ISO date, turn into an epoch number + local y, m, d = match(epoch, "^(%d%d%d%d)-(%d%d)-(%d%d)$") + if y then + epoch = + os_time({year = y, month = m, day = d, hour = 0, sec = 0, isdst = nil}) - + os_time({year = 1970, month = 1, day = 1, hour = 0, sec = 0, isdst = nil}) + elseif match(epoch, "^%d+$") then + epoch = tonumber(epoch) + else + epoch = 0 + end +end + +function setepoch() + return + os_setenv .. " SOURCE_DATE_EPOCH=" .. epoch + .. os_concat .. + os_setenv .. " SOURCE_DATE_EPOCH_TEX_PRIMITIVES=1" + .. os_concat .. + os_setenv .. " FORCE_SOURCE_DATE=1" + .. os_concat +end + +local function getscriptname() + if match(arg[0], "l3build$") or match(arg[0], "l3build%.lua$") then + return lookup("l3build.lua") + else + return arg[0] + end +end + +-- Do some subtarget for all modules in a bundle +function call(dirs, target, opts) + -- Turn the option table into a string + local opts = opts or options + local s = "" + for k,v in pairs(opts) do + if k ~= "names" and k ~= "target" then -- Special cases + local t = option_list[k] or { } + local arg = "" + if t["type"] == "string" then + arg = arg .. "=" .. v + end + if t["type"] == "table" then + for _,a in pairs(v) do + if arg == "" then + arg = "=" .. a -- Add the initial "=" here + else + arg = arg .. "," .. a + end + end + end + s = s .. " --" .. k .. arg + end + end + if opts["names"] then + for _,v in pairs(opts["names"]) do + s = s .. " " .. v + end + end + local scriptname = getscriptname() + for _,i in ipairs(dirs) do + local text = " for module " .. i + if i == "." and opts["config"] then + text = " with configuration " .. opts["config"][1] + end + print("Running l3build with target \"" .. target .. "\"" .. text ) + local errorlevel = run( + i, + "texlua " .. scriptname .. " " .. target .. s + ) + if errorlevel ~= 0 then + return errorlevel + end + end + return 0 +end + +-- Unpack files needed to support testing/typesetting/unpacking +function depinstall(deps) + local errorlevel + for _,i in ipairs(deps) do + print("Installing dependency: " .. i) + errorlevel = run(i, "texlua " .. getscriptname() .. " unpack -q") + if errorlevel ~= 0 then + return errorlevel + end + end + return 0 +end diff --git a/macros/latex/contrib/l3build/l3build-check.lua b/macros/latex/contrib/l3build/l3build-check.lua new file mode 100644 index 0000000000..c05defc792 --- /dev/null +++ b/macros/latex/contrib/l3build/l3build-check.lua @@ -0,0 +1,990 @@ +--[[ + +File l3build-check.lua Copyright (C) 2018,2019 The LaTeX3 Project + +It may be distributed and/or modified under the conditions of the +LaTeX Project Public License (LPPL), either version 1.3c of this +license or (at your option) any later version. The latest version +of this license is in the file + + http://www.latex-project.org/lppl.txt + +This file is part of the "l3build bundle" (The Work in LPPL) +and all files in that bundle must be distributed together. + +----------------------------------------------------------------------- + +The development version of the bundle can be found at + + https://github.com/latex3/l3build + +for those people who are interested. + +--]] + +-- Local access to functions +local open = io.open +local close = io.close +local write = io.write +local output = io.output + +local rnd = math.random + +local luatex_version = status.luatex_version + +local len = string.len +local char = string.char +local format = string.format +local gmatch = string.gmatch +local gsub = string.gsub +local match = string.match + +local insert = table.insert +local sort = table.sort + +local utf8_char = unicode.utf8.char + +local exit = os.exit +local execute = os.execute +local remove = os.remove + +-- +-- Auxiliary functions which are used by more than one main function +-- + +-- Set up the check system files: needed for checking one or more tests and +-- for saving the test files +function checkinit() + if not options["dirty"] then + cleandir(testdir) + end + depinstall(checkdeps) + -- Copy dependencies to the test directory itself: this makes the paths + -- a lot easier to manage, and is important for dealing with the log and + -- with file input/output tests + for _,i in ipairs(filelist(localdir)) do + cp(i, localdir, testdir) + end + bundleunpack({sourcefiledir, testfiledir}) + for _,i in ipairs(installfiles) do + cp(i, unpackdir, testdir) + end + for _,i in ipairs(checkfiles) do + cp(i, unpackdir, testdir) + end + if direxists(testsuppdir) then + for _,i in ipairs(filelist(testsuppdir)) do + cp(i, testsuppdir, testdir) + end + end + for _,i in ipairs(checksuppfiles) do + cp(i, supportdir, testdir) + end + execute(os_ascii .. ">" .. testdir .. "/ascii.tcx") +end + +local function rewrite(source,result,processor,...) + local file = assert(open(source,"rb")) + local content = gsub(file:read("*all") .. "\n","\r\n","\n") + close(file) + local new_content = processor(content,...) + local newfile = open(result,"w") + output(newfile) + write(new_content) + close(newfile) +end + +-- Convert the raw log file into one for comparison/storage: keeps only +-- the 'business' part from the tests and removes system-dependent stuff +local function normalize_log(content,engine,errlevels) + local maxprintline = maxprintline + if match(engine,"^lua") or match(engine,"^harf") then + maxprintline = maxprintline + 1 -- Deal with an out-by-one error + end + local function killcheck(line) + -- Skip \openin/\openout lines in web2c 7.x + -- As Lua doesn't allow "(in|out)", a slightly complex approach: + -- do a substitution to check the line is exactly what is required! + if match(gsub(line, "^\\openin", "\\openout"), "^\\openout%d%d? = ") then + return true + end + return false + end + -- Substitutions to remove some non-useful changes + local function normalize(line,lastline,drop_fd) + if drop_fd then + if match(line," *%)") then + return "","" + else + return "","",true + end + end + -- Zap line numbers from \show, \showbox, \box_show and the like: + -- do this before wrapping lines + line = gsub(line, "^l%.%d+ ", "l. ...") + -- Also from lua stack traces. + line = gsub(line, "lua:%d+: in function", "lua:...: in function") + -- Allow for wrapped lines: preserve the content and wrap + -- Skip lines that have an explicit marker for truncation + if len(line) == maxprintline and + not match(line, "%.%.%.$") then + return "", (lastline or "") .. line + end + local line = (lastline or "") .. line + lastline = "" + -- Zap ./ at begin of filename + line = gsub(line, "%(%.%/", "(") + -- Zap paths if places other than 'here' are accessible + if checksearch then + -- The pattern excludes < and > as the image part can have + -- several entries on one line + local pattern = "%w?:?/[^ %<%>]*/([^/%(%)]*%.%w*)" + -- Files loaded from TeX: all start ( -- ) + line = gsub(line, "%(" .. pattern, "(../%1") + -- Images + line = gsub(line, "<" .. pattern .. ">", "<../%1>") + -- luaotfload files start with keywords + line = gsub(line, "from " .. pattern .. "%(", "from. ./%1(") + line = gsub(line, ": " .. pattern .. "%)", ": ../%1)") + -- Deal with XeTeX specials + if match(line, "^%.+\\XeTeX.?.?.?file") then + line = gsub(line, pattern, "../%1") + end + end + -- Deal with dates + if match(line, "[^<]%d%d%d%d[/%-]%d%d[/%-]%d%d") then + line = gsub(line,"%d%d%d%d[/%-]%d%d[/%-]%d%d","....-..-..") + line = gsub(line,"v%d+%.?%d?%d?%w?","v...") + end + -- Deal with leading spaces for file and page number lines + line = gsub(line,"^ *%[(%d)","[%1") + line = gsub(line,"^ *%(","(") + -- Zap .fd lines: drop the first part, and skip to the end + if match(line, "^ *%([%.%/%w]+%.fd[^%)]*$") then + return "","",true + end + -- TeX90/XeTeX knows only the smaller set of dimension units + line = gsub(line, + "cm, mm, dd, cc, bp, or sp", + "cm, mm, dd, cc, nd, nc, bp, or sp") + -- On the other hand, (u)pTeX has some new units! + line = gsub(line, + "em, ex, zw, zh, in, pt, pc,", + "em, ex, in, pt, pc,") + line = gsub(line, + "cm, mm, dd, cc, bp, H, Q, or sp;", + "cm, mm, dd, cc, nd, nc, bp, or sp;") + -- Normalise a case where fixing a TeX bug changes the message text + line = gsub(line, "\\csname\\endcsname ", "\\csname\\endcsname") + -- Zap "on line " and replace with "on line ..." + -- Two similar cases, Lua patterns mean we need to do them separately + line = gsub(line, "on line %d*", "on line ...") + line = gsub(line, "on input line %d*", "on input line ...") + -- Tidy up to ^^ notation + for i = 0, 31 do + line = gsub(line, char(i), "^^" .. char(64 + i)) + end + -- Normalise register allocation to hard-coded numbers + -- No regex, so use a pattern plus lookup approach + local register_types = { + attribute = true, + box = true, + bytecode = true, + catcodetable = true, + count = true, + dimen = true, + insert = true, + language = true, + luabytecode = true, + luachunk = true, + luafunction = true, + marks = true, + muskip = true, + read = true, + skip = true, + toks = true, + whatsit = true, + write = true, + XeTeXcharclass = true + } + if register_types[match(line, "^\\[^%]]+=\\([a-z]+)%d+$")] then + line = gsub(line, "%d+$", "...") + end + -- Also deal with showing boxes + if match(line, "^> \\box%d+=$") or match(line, "^> \\box%d+=(void)$") then + line = gsub(line, "%d+=", "...=") + end + -- Remove 'normal' direction information on boxes with (u)pTeX + line = gsub(line, ",? yoko direction,?", "") + line = gsub(line, ",? yoko%(math%) direction,?", "") + -- Remove '\displace 0.0' lines in (u)pTeX + if match(line,"^%.*\\displace 0%.0$") then + return "" + end + -- Remove the \special line that in DVI mode keeps PDFs comparable + if match(line, "^%.*\\special%{pdf: docinfo << /Creator") then + return "" + end + if match(line, "^%.*\\special%{dvipdfmx:config") then + return "" + end + -- Remove the \special line possibly present in DVI mode for paper size + if match(line, "^%.*\\special%{papersize") then + return "" + end + -- Remove ConTeXt stuff + if match(line, "^backend >") or + match(line, "^close source >") or + match(line, "^mkiv lua stats >") or + match(line, "^pages >") or + match(line, "^system >") or + match(line, "^used file >") or + match(line, "^used option >") or + match(line, "^used structure >") then + return "" + end + -- The first time a new font is used by LuaTeX, it shows up + -- as being cached: make it appear loaded every time + line = gsub(line, "save cache:", "load cache:") + -- A tidy-up to keep LuaTeX and other engines in sync + line = gsub(line, utf8_char(127), "^^?") + -- Unicode engines display chars in the upper half of the 8-bit range: + -- tidy up to match pdfTeX if an ASCII engine is in use + if next(asciiengines) then + for i = 128, 255 do + line = gsub(line, utf8_char(i), "^^" .. format("%02x", i)) + end + end + return line, lastline + end + local lastline = "" + local drop_fd = false + local new_content = "" + local prestart = true + local skipping = false + for line in gmatch(content, "([^\n]*)\n") do + if line == "START-TEST-LOG" then + prestart = false + elseif line == "END-TEST-LOG" or + match(line, "^Here is how much of .?.?.?TeX\'s memory you used:") then + break + elseif line == "OMIT" then + skipping = true + elseif match(line, "^%)?TIMO$") then + skipping = false + elseif not prestart and not skipping then + line, lastline, drop_fd = normalize(line, lastline,drop_fd) + if not match(line, "^ *$") and not killcheck(line) then + new_content = new_content .. line .. os_newline + end + end + end + if recordstatus then + new_content = new_content .. '***************' .. os_newline + for i = 1, checkruns do + new_content = new_content .. + 'Compilation ' .. i .. ' of test file completed with exit status ' .. + errlevels[i] .. os_newline + end + end + return new_content +end + +-- Additional normalization for LuaTeX +local function normalize_lua_log(content,luatex) + local function normalize(line, lastline, dropping) + -- Find \discretionary or \whatsit lines: + -- These may come back later + if match(line, "^%.+\\discretionary$") or + match(line, "^%.+\\discretionary %(penalty 50%)$") or + match(line, "^%.+\\discretionary50%|$") or + match(line, "^%.+\\discretionary50%| replacing $") or + match(line, "^%.+\\whatsit$") then + return "", line + end + -- For \mathon, we always need this line but the next + -- may be affected + if match(line, "^%.+\\mathon$") then + return line, line + end + -- LuaTeX has a flexible output box + line = gsub(line,"\\box\\outputbox", "\\box255") + -- LuaTeX identifies spaceskip glue + line = gsub(line,"%(\\spaceskip%) ", " ") + -- Remove 'display' at end of display math boxes: + -- LuaTeX omits this as it includes direction in all cases + line = gsub(line, "(\\hbox%(.*), display$", "%1") + -- Remove 'normal' direction information on boxes: + -- any bidi/vertical stuff will still show + line = gsub(line, ", direction TLT", "") + -- Find glue setting and round out the last place + local function round_digits(l, m) + return gsub( + l, + m .. " (%-?)%d+%.%d+", + m .. " %1" + .. format( + "%.3f", + match(line, m .. " %-?(%d+%.%d+)") or 0 + ) + ) + end + if match(line, "glue set %-?%d+%.%d+") then + line = round_digits(line, "glue set") + end + if match( + line, "glue %-?%d+%.%d+ plus %-?%d+%.%d+ minus %-?%d+%.%d+$" + ) + then + line = round_digits(line, "glue") + line = round_digits(line, "plus") + line = round_digits(line, "minus") + end + -- LuaTeX writes ^^M as a new line, which we lose + line = gsub(line, "%^%^M", "") + -- Remove U+ notation in the "Missing character" message + line = gsub( + line, + "Missing character: There is no (%^%^..) %(U%+(....)%)", + "Missing character: There is no %1" + ) + -- LuaTeX from v1.07 logs kerns differently ... + -- This block only applies to the output of LuaTeX itself, + -- hence needing a flag to skip the case of the reference log + if luatex and + tonumber(luatex_version) >= 107 and + match(line, "^%.*\\kern") then + -- Re-insert the space in explicit kerns + if match(line, "kern%-?%d+%.%d+ *$") then + line = gsub(line, "kern", "kern ") + elseif match(line, "%(accent%)$") then + line = gsub(line, "kern", "kern ") + line = gsub(line, "%(accent%)$", "(for accent)") + elseif match(line, "%(italic%)$") then + line = gsub(line, "kern", "kern ") + line = gsub(line, " %(italic%)$", "") + else + line = gsub(line, " %(font%)$", "") + end + end + -- Changes in PDF specials + line = gsub(line, "\\pdfliteral origin", "\\pdfliteral") + -- A function to handle the box prefix part + local function boxprefix(s) + return gsub(match(s, "^(%.+)"), "%.", "%%.") + end + -- 'Recover' some discretionary data + if match(lastline, "^%.+\\discretionary %(penalty 50%)$") and + match(line, boxprefix(lastline) .. "%.= ") then + line = gsub(line," %(font%)$","") + return gsub(line, "%.= ", ""),"" + end + -- Where the last line was a discretionary, looks for the + -- info one level in about what it represents + if match(lastline, "^%.+\\discretionary$") or + match(lastline, "^%.+\\discretionary %(penalty 50%)$") or + match(lastline, "^%.+\\discretionary50%|$") or + match(lastline, "^%.+\\discretionary50%| replacing $") then + local prefix = boxprefix(lastline) + if match(line, prefix .. "%.") or + match(line, prefix .. "%|") then + if match(lastline, " replacing $") and + not dropping then + -- Modify the return line + return gsub(line, "^%.", ""), lastline, true + else + return "", lastline, true + end + else + if dropping then + -- End of a \discretionary block + return line, "" + else + -- Not quite a normal discretionary + if match(lastline, "^%.+\\discretionary50%|$") then + lastline = gsub(lastline, "50%|$", "") + end + -- Remove some info that TeX90 lacks + lastline = gsub(lastline, " %(penalty 50%)$", "") + -- A normal (TeX90) discretionary: + -- add with the line break reintroduced + return lastline .. os_newline .. line, "" + end + end + end + -- Look for another form of \discretionary, replacing a "-" + pattern = "^%.+\\discretionary replacing *$" + if match(line, pattern) then + return "", line + else + if match(lastline, pattern) then + local prefix = boxprefix(lastline) + if match(line, prefix .. "%.\\kern") then + return gsub(line, "^%.", ""), lastline, true + elseif dropping then + return "", "" + else + return lastline .. os_newline .. line, "" + end + end + end + -- For \mathon, if the current line is an empty \hbox then + -- drop it + if match(lastline, "^%.+\\mathon$") then + local prefix = boxprefix(lastline) + if match(line, prefix .. "\\hbox%(0%.0%+0%.0%)x0%.0$") then + return "", "" + end + end + -- Various \local... things that other engines do not do: + -- Only remove the no-op versions + if match(line, "^%.+\\localpar$") or + match(line, "^%.+\\localinterlinepenalty=0$") or + match(line, "^%.+\\localbrokenpenalty=0$") or + match(line, "^%.+\\localleftbox=null$") or + match(line, "^%.+\\localrightbox=null$") then + return "", "" + end + -- Older LuaTeX versions set the above up as a whatsit + -- (at some stage this can therefore go) + if match(lastline, "^%.+\\whatsit$") then + local prefix = boxprefix(lastline) + if match(line, prefix .. "%.") then + return "", lastline, true + else + -- End of a \whatsit block + return line, "" + end + end + -- Wrap some cases that can be picked out + -- In some places LuaTeX does use max_print_line, then we + -- get into issues with different wrapping approaches + if len(line) == maxprintline then + return "", lastline .. line + elseif len(lastline) == maxprintline then + if match(line, "\\ETC%.%}$") then + -- If the line wrapped at \ETC we might have lost a space + return lastline + .. ((match(line, "^\\ETC%.%}$") and " ") or "") + .. line, "" + elseif match(line, "^%}%}%}$") then + return lastline .. line, "" + else + return lastline .. os_newline .. line, "" + end + -- Return all of the text for a wrapped (multi)line + elseif len(lastline) > maxprintline then + return lastline .. line, "" + end + -- Remove spaces at the start of lines: deals with the fact that LuaTeX + -- uses a different number to the other engines + return gsub(line, "^%s+", ""), "" + end + local new_content = "" + local lastline = "" + local dropping = false + for line in gmatch(content, "([^\n]*)\n") do + line, lastline, dropping = normalize(line, lastline, dropping) + if not match(line, "^ *$") then + new_content = new_content .. line .. os_newline + end + end + return new_content +end + +local function normalize_pdf(content) + local new_content = "" + local stream_content = "" + local binary = false + local stream = false + for line in gmatch(content,"([^\n]*)\n") do + if stream then + if match(line,"endstream") then + stream = false + if binary then + new_content = new_content .. "[BINARY STREAM]" .. os_newline + else + new_content = new_content .. stream_content .. line .. os_newline + end + binary = false + else + for i = 0, 31 do + if match(line,char(i)) then + binary = true + break + end + end + if not binary and not match(line, "^ *$") then + stream_content = stream_content .. line .. os_newline + end + end + elseif match(line,"^stream$") then + binary = false + stream = true + stream_content = "stream" .. os_newline + elseif not match(line, "^ *$") then + new_content = new_content .. line .. os_newline + end + end + return new_content +end + +-- Run one test which may have multiple engine-dependent comparisons +-- Should create a difference file for each failed test +function runcheck(name, hide) + if not testexists(name) then + print("Failed to find input for test " .. name) + return 1 + end + local checkengines = checkengines + if options["engine"] then + checkengines = options["engine"] + end + -- Used for both .lvt and .pvt tests + local function check_and_diff(ext,engine,comp,pdftest) + runtest(name,engine,hide,ext,pdftest,true) + local errorlevel = comp(name,engine) + if errorlevel == 0 then + return errorlevel + end + if options["show-log-on-error"] then + showfailedlog(name) + end + if options["halt-on-error"] then + showfaileddiff() + end + return errorlevel + end + local errorlevel = 0 + for _,engine in pairs(checkengines) do + setup_check(name,engine) + local errlevel = 0 + if fileexists(testfiledir .. "/" .. name .. pvtext) then + errlevel = check_and_diff(pvtext,engine,compare_pdf,true) + else + errlevel = check_and_diff(lvtext,engine,compare_tlg) + end + if errlevel ~= 0 and options["halt-on-error"] then + return 1 + end + if errlevel > errorlevel then + errorlevel = errlevel + end + end + -- Return everything + return errorlevel +end + +function setup_check(name, engine) + local testname = name .. "." .. engine + local tlgfile = locate( + {testfiledir, unpackdir}, + {testname .. tlgext, name .. tlgext} + ) + local tpffile = locate( + {testfiledir, unpackdir}, + {testname .. tpfext, name .. tpfext} + ) + -- Attempt to generate missing reference file from expectation + if not (tlgfile or tpffile) then + if not locate({unpackdir, testfiledir}, {name .. lveext}) then + print( + "Error: failed to find " .. tlgext .. ", " .. tpfext .. " or " + .. lveext .. " file for " .. name .. "!" + ) + exit(1) + end + runtest(name, engine, true, lveext) + ren(testdir, testname .. logext, testname .. tlgext) + else + -- Install comparison files found + for _,v in pairs({tlgfile, tpffile}) do + if v then + cp( + match(v, ".*/(.*)"), + match(v, "(.*)/.*"), + testdir + ) + end + end + end +end + +function compare_pdf(name,engine,cleanup) + local testname = name .. "." .. engine + local difffile = testdir .. "/" .. testname .. pdfext .. os_diffext + local pdffile = testdir .. "/" .. testname .. pdfext + local tpffile = locate({testdir}, {testname .. tpfext, name .. tpfext}) + if not tpffile then + return 1 + end + local errorlevel = execute(os_diffexe .. " " + .. normalize_path(tpffile .. " " .. pdffile .. " > " .. difffile)) + if errorlevel == 0 or cleanup then + remove(difffile) + end + return errorlevel +end + +function compare_tlg(name,engine,cleanup) + local errorlevel + local testname = name .. "." .. engine + local difffile = testdir .. "/" .. testname .. os_diffext + local logfile = testdir .. "/" .. testname .. logext + local tlgfile = locate({testdir}, {testname .. tlgext, name .. tlgext}) + if not tlgfile then + return 1 + end + -- Do additional log formatting if the engine is LuaTeX, there is no + -- LuaTeX-specific .tlg file and the default engine is not LuaTeX + if (match(engine,"^lua") or match(engine,"^harf")) + and not match(tlgfile, "%.luatex" .. "%" .. tlgext) + and not match(stdengine,"^lua") + then + local lualogfile = logfile + if cleanup then + lualogfile = testdir .. "/" .. testname .. ".tmp" .. logext + end + local luatlgfile = testdir .. "/" .. testname .. tlgext + rewrite(tlgfile,luatlgfile,normalize_lua_log) + rewrite(logfile,lualogfile,normalize_lua_log,true) + errorlevel = execute(os_diffexe .. " " + .. normalize_path(luatlgfile .. " " .. lualogfile .. " > " .. difffile)) + if cleanup then + remove(lualogfile) + remove(luatlgfile) + end + else + errorlevel = execute(os_diffexe .. " " + .. normalize_path(tlgfile .. " " .. logfile .. " > " .. difffile)) + end + if errorlevel == 0 or cleanup then + remove(difffile) + end + return errorlevel +end + +-- Run one of the test files: doesn't check the result so suitable for +-- both creating and verifying +function runtest(name, engine, hide, ext, pdfmode, breakout) + local lvtfile = name .. (ext or lvtext) + cp(lvtfile, fileexists(testfiledir .. "/" .. lvtfile) + and testfiledir or unpackdir, testdir) + local checkopts = checkopts + local engine = engine or stdengine + local binary = engine + local format = gsub(engine,"tex$",checkformat) + -- Special binary/format combos + if specialformats[checkformat] and next(specialformats[checkformat]) then + local t = specialformats[checkformat] + if t[engine] and next(t[engine]) then + local t = t[engine] + binary = t.binary or binary + checkopts = t.options or checkopts + format = t.format or format + end + end + -- Finalise format string + if format ~= "" then + format = " --fmt=" .. format + end + -- Special casing for XeTeX engine + if match(engine, "xetex") and not pdfmode then + checkopts = checkopts .. " -no-pdf" + end + -- Special casing for ConTeXt + local function setup(file) + return " -jobname=" .. name .. " " .. ' "\\input ' .. file .. '" ' + end + if match(checkformat,"^context$") then + function setup(file) return ' "' .. file .. '" ' end + end + local basename = testdir .. "/" .. name + local logfile = basename .. logext + local newfile = basename .. "." .. engine .. logext + local pdffile = basename .. pdfext + local npffile = basename .. "." .. engine .. pdfext + local asciiopt = "" + for _,i in ipairs(asciiengines) do + if binary == i then + asciiopt = "-translate-file ./ascii.tcx " + break + end + end + -- Clean out any dynamic files + for _,filetype in pairs(dynamicfiles) do + rm(testdir,filetype) + end + -- Ensure there is no stray .log file + rm(testdir,name .. logext) + local errlevels = {} + for i = 1, checkruns do + errlevels[i] = run( + testdir, + -- No use of localdir here as the files get copied to testdir: + -- avoids any paths in the logs + os_setenv .. " TEXINPUTS=." .. (checksearch and os_pathsep or "") + .. os_concat .. + os_setenv .. " LUAINPUTS=." .. (checksearch and os_pathsep or "") + .. os_concat .. + -- Avoid spurious output from (u)pTeX + os_setenv .. " GUESS_INPUT_KANJI_ENCODING=0" + .. os_concat .. + -- Allow for local texmf files + os_setenv .. " TEXMFCNF=." .. os_pathsep + .. os_concat .. + (forcecheckepoch and setepoch() or "") .. + -- Ensure lines are of a known length + os_setenv .. " max_print_line=" .. maxprintline + .. os_concat .. + binary .. format + .. " " .. asciiopt .. " " .. checkopts + .. setup(lvtfile) + .. (hide and (" > " .. os_null) or "") + .. os_concat .. + runtest_tasks(jobname(lvtfile)) + ) + -- Break the loop if the result is stable + if breakout and i < checkruns then + if pdfmode then + rewrite(pdffile,npffile,normalize_pdf) + if compare_pdf(name,engine,true) == 0 then + break + end + else + rewrite(logfile,newfile,normalize_log,engine,errlevels) + if compare_tlg(name,engine,true) == 0 then + break + end + end + end + end + if pdfmode and fileexists(testdir .. "/" .. name .. dviext) then + dvitopdf(name, testdir, engine, hide) + end + if pdfmode then + rewrite(pdffile,npffile,normalize_pdf) + else + rewrite(logfile,newfile,normalize_log,engine,errlevels) + end + -- Store secondary files for this engine + for _,filetype in pairs(auxfiles) do + for _,file in pairs(filelist(testdir, filetype)) do + if match(file,"^" .. name .. ".[^.]+$") then + local ext = match(file, "%.[^.]+$") + if ext ~= lvtext and + ext ~= tlgext and + ext ~= lveext and + ext ~= logext then + local newname = gsub(file,"(%.[^.]+)$","." .. engine .. "%1") + if fileexists(testdir,newname) then + rm(testdir,newname) + end + ren(testdir,file,newname) + end + end + end + end + return 0 +end + +-- A hook to allow additional tasks to run for the tests +runtest_tasks = runtest_tasks or function(name) + return "" +end + +-- Look for a test: could be in the testfiledir or the unpackdir +function testexists(test) + return(locate({testfiledir, unpackdir}, + {test .. lvtext, test .. pvtext})) +end + +function check(names) + local errorlevel = 0 + if testfiledir ~= "" and direxists(testfiledir) then + if not options["rerun"] then + checkinit() + end + local hide = true + if names and next(names) then + hide = false + end + names = names or { } + -- No names passed: find all test files + if not next(names) then + local excludenames = { } + for _,glob in pairs(excludetests) do + for _,name in pairs(filelist(testfiledir, glob .. lvtext)) do + excludenames[jobname(name)] = true + end + for _,name in pairs(filelist(unpackdir, glob .. lvtext)) do + excludenames[jobname(name)] = true + end + for _,name in pairs(filelist(testfiledir, glob .. pvtext)) do + excludenames[jobname(name)] = true + end + end + local function addname(name) + if not excludenames[jobname(name)] then + insert(names,jobname(name)) + end + end + for _,glob in pairs(includetests) do + for _,name in pairs(filelist(testfiledir, glob .. lvtext)) do + addname(name) + end + for _,name in pairs(filelist(testfiledir, glob .. pvtext)) do + addname(name) + end + for _,name in pairs(filelist(unpackdir, glob .. lvtext)) do + if fileexists(testfiledir .. "/" .. name) then + print("Duplicate test file: " .. i) + return 1 + end + addname(name) + end + end + sort(names) + -- Deal limiting range of names + if options["first"] then + local allnames = names + local active = false + local firstname = options["first"] + names = { } + for _,name in ipairs(allnames) do + if name == firstname then + active = true + end + if active then + insert(names,name) + end + end + end + if options["last"] then + local allnames = names + local lastname = options["last"] + names = { } + for _,name in ipairs(allnames) do + insert(names,name) + if name == lastname then + break + end + end + end + end + -- https://stackoverflow.com/a/32167188 + local function shuffle(tbl) + local len, random = #tbl, rnd + for i = len, 2, -1 do + local j = random(1, i) + tbl[i], tbl[j] = tbl[j], tbl[i] + end + return tbl + end + if options["shuffle"] then + names = shuffle(names) + end + -- Actually run the tests + print("Running checks on") + local i = 0 + for _,name in ipairs(names) do + i = i + 1 + print(" " .. name .. " (" .. i.. "/" .. #names ..")") + local errlevel = runcheck(name, hide) + -- Return value must be 1 not errlevel + if errlevel ~= 0 then + if options["halt-on-error"] then + return 1 + else + errorlevel = 1 + -- visually show that something has failed + print(" --> failed\n") + end + end + end + if errorlevel ~= 0 then + checkdiff() + else + print("\n All checks passed\n") + end + end + return errorlevel +end + +-- A short auxiliary to print the list of differences for check +function checkdiff() + print("\n Check failed with difference files") + for _,i in ipairs(filelist(testdir, "*" .. os_diffext)) do + print(" - " .. testdir .. "/" .. i) + end + print("") +end + +function showfailedlog(name) + print("\nCheck failed with log file") + for _,i in ipairs(filelist(testdir, name..".log")) do + print(" - " .. testdir .. "/" .. i) + print("") + local f = open(testdir .. "/" .. i,"r") + local content = f:read("*all") + close(f) + print("-----------------------------------------------------------------------------------") + print(content) + print("-----------------------------------------------------------------------------------") + end +end + +function showfaileddiff() + print("\nCheck failed with difference file") + for _,i in ipairs(filelist(testdir, "*" .. os_diffext)) do + print(" - " .. testdir .. "/" .. i) + print("") + local f = open(testdir .. "/" .. i,"r") + local content = f:read("*all") + close(f) + print("-----------------------------------------------------------------------------------") + print(content) + print("-----------------------------------------------------------------------------------") + end +end + +function save(names) + checkinit() + local engines = options["engine"] or {stdengine} + for _,name in pairs(names) do + if testexists(name) then + for _,engine in pairs(engines) do + local testengine = ((engine == stdengine and "") or "." .. engine) + local function save_test(test_ext,gen_ext,out_ext,pdfmode) + local out_file = name .. testengine .. out_ext + local gen_file = name .. "." .. engine .. gen_ext + print("Creating and copying " .. out_file) + runtest(name,engine,false,test_ext,pdfmode) + ren(testdir,gen_file,out_file) + cp(out_file,testdir,testfiledir) + if fileexists(unpackdir .. "/" .. out_file) then + print("Saved " .. out_ext + .. " file overrides unpacked version of the same name") + return 1 + end + return 0 + end + local errorlevel + if fileexists(testfiledir .. "/" .. name .. lvtext) then + errorlevel = save_test(lvtext,logext,tlgext) + else + errorlevel = save_test(pvtext,pdfext,tpfext,true) + end + if errorlevel ~=0 then return errorlevel end + end + elseif locate({unpackdir, testfiledir}, {name .. lveext}) then + print("Saved " .. tlgext .. " file overrides a " + .. lveext .. " file of the same name") + return 1 + else + print('Test "'.. name .. '"not found') + return 1 + end + end + return 0 +end diff --git a/macros/latex/contrib/l3build/l3build-clean.lua b/macros/latex/contrib/l3build/l3build-clean.lua new file mode 100644 index 0000000000..01927a71d0 --- /dev/null +++ b/macros/latex/contrib/l3build/l3build-clean.lua @@ -0,0 +1,56 @@ +--[[ + +File l3build-clean.lua Copyright (C) 2018 The LaTeX3 Project + +It may be distributed and/or modified under the conditions of the +LaTeX Project Public License (LPPL), either version 1.3c of this +license or (at your option) any later version. The latest version +of this license is in the file + + http://www.latex-project.org/lppl.txt + +This file is part of the "l3build bundle" (The Work in LPPL) +and all files in that bundle must be distributed together. + +----------------------------------------------------------------------- + +The development version of the bundle can be found at + + https://github.com/latex3/l3build + +for those people who are interested. + +--]] + +-- Remove all generated files +function clean() + -- To make sure that distribdir never contains any stray subdirs, + -- it is entirely removed then recreated rather than simply deleting + -- all of the files + local errorlevel = + rmdir(distribdir) + + mkdir(distribdir) + + cleandir(localdir) + + cleandir(testdir) + + cleandir(typesetdir) + + cleandir(unpackdir) + for _,i in ipairs(cleanfiles) do + for _,dir in pairs(remove_duplicates({maindir, sourcefiledir, docfiledir})) do + errorlevel = rm(dir, i) + errorlevel + end + end + return errorlevel +end + +function bundleclean() + local errorlevel = call(modules, "clean") + for _,i in ipairs(cleanfiles) do + errorlevel = rm(currentdir, i) + errorlevel + end + return ( + errorlevel + + rmdir(ctandir) + + rmdir(tdsdir) + ) +end + diff --git a/macros/latex/contrib/l3build/l3build-ctan.lua b/macros/latex/contrib/l3build/l3build-ctan.lua new file mode 100644 index 0000000000..09b10e0eca --- /dev/null +++ b/macros/latex/contrib/l3build/l3build-ctan.lua @@ -0,0 +1,159 @@ +--[[ + +File l3build-ctan.lua Copyright (C) 2018 The LaTeX3 Project + +It may be distributed and/or modified under the conditions of the +LaTeX Project Public License (LPPL), either version 1.3c of this +license or (at your option) any later version. The latest version +of this license is in the file + + http://www.latex-project.org/lppl.txt + +This file is part of the "l3build bundle" (The Work in LPPL) +and all files in that bundle must be distributed together. + +----------------------------------------------------------------------- + +The development version of the bundle can be found at + + https://github.com/latex3/l3build + +for those people who are interested. + +--]] + +local pairs = pairs +local print = print + +local lower = string.lower +local match = string.match + +-- Copy files to the main CTAN release directory +function copyctan() + mkdir(ctandir .. "/" .. ctanpkg) + local function copyfiles(files,source) + if source == currentdir or flatten then + for _,filetype in pairs(files) do + cp(filetype,source,ctandir .. "/" .. ctanpkg) + end + else + for _,filetype in pairs(files) do + for file,_ in pairs(tree(source,filetype)) do + local path = splitpath(file) + local ctantarget = ctandir .. "/" .. ctanpkg .. "/" + .. source .. "/" .. path + mkdir(ctantarget) + cp(file,source,ctantarget) + end + end + end + end + for _,tab in pairs( + {bibfiles,demofiles,docfiles,pdffiles,scriptmanfiles,typesetlist}) do + copyfiles(tab,docfiledir) + end + copyfiles(sourcefiles,sourcefiledir) + for _,file in pairs(textfiles) do + cp(file, currentdir, ctandir .. "/" .. ctanpkg) + end +end + +function bundlectan() + local errorlevel = install_files(tdsdir,true) + if errorlevel ~=0 then return errorlevel end + copyctan() + return 0 +end + +function ctan() + -- Always run tests for all engines + options["engine"] = nil + local function dirzip(dir, name) + local zipname = name .. ".zip" + local function tab_to_str(table) + local string = "" + for _,i in ipairs(table) do + string = string .. " " .. "\"" .. i .. "\"" + end + return string + end + -- Convert the tables of files to quoted strings + local binfiles = tab_to_str(binaryfiles) + local exclude = tab_to_str(excludefiles) + -- First, zip up all of the text files + run( + dir, + zipexe .. " " .. zipopts .. " -ll ".. zipname .. " " .. "." + .. ( + (binfiles or exclude) and (" -x" .. binfiles .. " " .. exclude) + or "" + ) + ) + -- Then add the binary ones + run( + dir, + zipexe .. " " .. zipopts .. " -g ".. zipname .. " " .. ". -i" .. + binfiles .. (exclude and (" -x" .. exclude) or "") + ) + end + local errorlevel + local standalone = false + if bundle == "" then + standalone = true + end + if standalone then + errorlevel = call({"."},"check") + bundle = module + else + errorlevel = call(modules, "bundlecheck") + end + if errorlevel == 0 then + rmdir(ctandir) + mkdir(ctandir .. "/" .. ctanpkg) + rmdir(tdsdir) + mkdir(tdsdir) + if standalone then + errorlevel = install_files(tdsdir,true) + if errorlevel ~=0 then return errorlevel end + copyctan() + else + errorlevel = call(modules, "bundlectan") + end + else + print("\n====================") + print("Tests failed, zip stage skipped!") + print("====================\n") + return errorlevel + end + if errorlevel == 0 then + for _,i in ipairs(textfiles) do + for _,j in pairs({unpackdir, currentdir}) do + cp(i, j, ctandir .. "/" .. ctanpkg) + cp(i, j, tdsdir .. "/doc/" .. tdsroot .. "/" .. bundle) + end + end + -- Rename README if necessary + if ctanreadme ~= "" and not match(lower(ctanreadme),"^readme%.%w+") then + local newfile = "README." .. match(ctanreadme,"%.(%w+)$") + for _,dir in pairs({ctandir .. "/" .. ctanpkg, + tdsdir .. "/doc/" .. tdsroot .. "/" .. bundle}) do + if fileexists(dir .. "/" .. ctanreadme) then + rm(dir,newfile) + ren(dir,ctanreadme,newfile) + end + end + end + dirzip(tdsdir, ctanpkg .. ".tds") + if packtdszip then + cp(ctanpkg .. ".tds.zip", tdsdir, ctandir) + end + dirzip(ctandir, ctanzip) + cp(ctanzip .. ".zip", ctandir, currentdir) + else + print("\n====================") + print("Typesetting failed, zip stage skipped!") + print("====================\n") + end + return errorlevel +end + diff --git a/macros/latex/contrib/l3build/l3build-file-functions.lua b/macros/latex/contrib/l3build/l3build-file-functions.lua new file mode 100644 index 0000000000..9c7ac1fb6d --- /dev/null +++ b/macros/latex/contrib/l3build/l3build-file-functions.lua @@ -0,0 +1,444 @@ +--[[ + +File l3build-file-functions.lua Copyright (C) 2018,2019 The LaTeX3 Project + +It may be distributed and/or modified under the conditions of the +LaTeX Project Public License (LPPL), either version 1.3c of this +license or (at your option) any later version. The latest version +of this license is in the file + + http://www.latex-project.org/lppl.txt + +This file is part of the "l3build bundle" (The Work in LPPL) +and all files in that bundle must be distributed together. + +----------------------------------------------------------------------- + +The development version of the bundle can be found at + + https://github.com/latex3/l3build + +for those people who are interested. + +--]] + +local pairs = pairs +local print = print + +local open = io.open + +local attributes = lfs.attributes +local currentdir = lfs.currentdir +local chdir = lfs.chdir +local lfs_dir = lfs.dir + +local execute = os.execute +local exit = os.exit +local getenv = os.getenv +local remove = os.remove +local os_time = os.time +local os_type = os.type + +local luatex_revision = status.luatex_revision +local luatex_version = status.luatex_version + +local match = string.match +local sub = string.sub +local gmatch = string.gmatch +local gsub = string.gsub + +local insert = table.insert + +-- Convert a file glob into a pattern for use by e.g. string.gub +-- Based on https://github.com/davidm/lua-glob-pattern +-- Simplified substantially: "[...]" syntax not supported as is not +-- required by the file patterns used by the team. Also note style +-- changes to match coding approach in rest of this file. +-- +-- License for original globtopattern +--[[ + + (c) 2008-2011 David Manura. Licensed under the same terms as Lua (MIT). + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + (end license) + +--]] +function glob_to_pattern(glob) + + local pattern = "^" -- pattern being built + local i = 0 -- index in glob + local char -- char at index i in glob + + -- escape pattern char + local function escape(char) + return match(char, "^%w$") and char or "%" .. char + end + + -- Convert tokens. + while true do + i = i + 1 + char = sub(glob, i, i) + if char == "" then + pattern = pattern .. "$" + break + elseif char == "?" then + pattern = pattern .. "." + elseif char == "*" then + pattern = pattern .. ".*" + elseif char == "[" then + -- Ignored + print("[...] syntax not supported in globs!") + elseif char == "\\" then + i = i + 1 + char = sub(glob, i, i) + if char == "" then + pattern = pattern .. "\\$" + break + end + pattern = pattern .. escape(char) + else + pattern = pattern .. escape(char) + end + end + return pattern +end + +-- Detect the operating system in use +-- Support items are defined here for cases where a single string can cover +-- both Windows and Unix cases: more complex situations are handled inside +-- the support functions +os_concat = ";" +os_null = "/dev/null" +os_pathsep = ":" +os_setenv = "export" +os_yes = "printf 'y\\n%.0s' {1..200}" + +os_ascii = "echo \"\"" +os_cmpexe = getenv("cmpexe") or "cmp" +os_cmpext = getenv("cmpext") or ".cmp" +os_diffext = getenv("diffext") or ".diff" +os_diffexe = getenv("diffexe") or "diff -c --strip-trailing-cr" +os_grepexe = "grep" +os_newline = "\n" + +if os_type == "windows" then + os_ascii = "@echo." + os_cmpexe = getenv("cmpexe") or "fc /b" + os_cmpext = getenv("cmpext") or ".cmp" + os_concat = "&" + os_diffext = getenv("diffext") or ".fc" + os_diffexe = getenv("diffexe") or "fc /n" + os_grepexe = "findstr /r" + os_newline = "\n" + if tonumber(luatex_version) < 100 or + (tonumber(luatex_version) == 100 + and tonumber(luatex_revision) < 4) then + os_newline = "\r\n" + end + os_null = "nul" + os_pathsep = ";" + os_setenv = "set" + os_yes = "for /l %I in (1,1,200) do @echo y" +end + +-- Deal with the fact that Windows and Unix use different path separators +local function unix_to_win(path) + return gsub(path, "/", "\\") +end + +function normalize_path(path) + if os_type == "windows" then + return unix_to_win(path) + end + return path +end + +-- Return an absolute path from a relative one +function abspath(path) + local oldpwd = currentdir() + chdir(path) + local result = currentdir() + chdir(oldpwd) + return escapepath(gsub(result, "\\", "/")) +end + +function escapepath(path) + if os_type == "windows" then + local path,count = gsub(path,'"','') + if count % 2 ~= 0 then + print("Unbalanced quotes in path") + exit(0) + else + if match(path," ") then + return '"' .. path .. '"' + end + return path + end + else + path = gsub(path,"\\ ","[PATH-SPACE]") + path = gsub(path," ","\\ ") + return gsub(path,"%[PATH-SPACE%]","\\ ") + end +end + +-- For cleaning out a directory, which also ensures that it exists +function cleandir(dir) + local errorlevel = mkdir(dir) + if errorlevel ~= 0 then + return errorlevel + end + return rm(dir, "*") +end + +-- Copy files 'quietly' +function cp(glob, source, dest) + local errorlevel + for i,_ in pairs(tree(source, glob)) do + local source = source .. "/" .. i + if os_type == "windows" then + if attributes(source)["mode"] == "directory" then + errorlevel = execute( + 'xcopy /y /e /i "' .. unix_to_win(source) .. '" "' + .. unix_to_win(dest .. '/' .. i) .. '" > nul' + ) + else + errorlevel = execute( + 'xcopy /y "' .. unix_to_win(source) .. '" "' + .. unix_to_win(dest .. '/') .. '" > nul' + ) + end + else + errorlevel = execute("cp -RLf '" .. source .. "' '" .. dest .. "'") + end + if errorlevel ~=0 then + return errorlevel + end + end + return 0 +end + +-- OS-dependent test for a directory +function direxists(dir) + local errorlevel + if os_type == "windows" then + errorlevel = + execute("if not exist \"" .. unix_to_win(dir) .. "\" exit 1") + else + errorlevel = execute("[ -d " .. dir .. " ]") + end + if errorlevel ~= 0 then + return false + end + return true +end + +function fileexists(file) + local f = open(file, "r") + if f ~= nil then + f:close() + return true + else + return false + end +end + +-- Generate a table containing all file names of the given glob or all files +-- if absent +function filelist(path, glob) + local files = { } + local pattern + if glob then + pattern = glob_to_pattern(glob) + end + if direxists(path) then + for entry in lfs_dir(path) do + if pattern then + if match(entry, pattern) then + insert(files, entry) + end + else + if entry ~= "." and entry ~= ".." then + insert(files, entry) + end + end + end + end + return files +end + +-- Does what filelist does, but can also glob subdirectories. In the returned +-- table, the keys are paths relative to the given starting path, the values +-- are their counterparts relative to the current working directory. +function tree(path, glob) + local function cropdots(path) + return gsub(gsub(path, "^%./", ""), "/%./", "/") + end + local function always_true() + return true + end + local function is_dir(file) + return attributes(file)["mode"] == "directory" + end + local dirs = {["."] = cropdots(path)} + for pattern, criterion in gmatch(cropdots(glob), "([^/]+)(/?)") do + local criterion = criterion == "/" and is_dir or always_true + function fill(path, dir, table) + for _, file in ipairs(filelist(dir, pattern)) do + local fullpath = path .. "/" .. file + if file ~= "." and file ~= ".." and + fullpath ~= builddir + then + local fulldir = dir .. "/" .. file + if criterion(fulldir) then + table[fullpath] = fulldir + end + end + end + end + local newdirs = {} + if pattern == "**" then + while true do + path, dir = next(dirs) + if not path then + break + end + dirs[path] = nil + newdirs[path] = dir + fill(path, dir, dirs) + end + else + for path, dir in pairs(dirs) do + fill(path, dir, newdirs) + end + end + dirs = newdirs + end + return dirs +end + +function remove_duplicates(a) + -- Return array with duplicate entries removed from input array `a`. + + local uniq = {} + local hash = {} + + for _,v in ipairs(a) do + if (not hash[v]) then + hash[v] = true + uniq[#uniq+1] = v + end + end + + return uniq +end + +function mkdir(dir) + if os_type == "windows" then + -- Windows (with the extensions) will automatically make directory trees + -- but issues a warning if the dir already exists: avoid by including a test + local dir = unix_to_win(dir) + return execute( + "if not exist " .. dir .. "\\nul " .. "mkdir " .. dir + ) + else + return execute("mkdir -p " .. dir) + end +end + +-- Rename +function ren(dir, source, dest) + local dir = dir .. "/" + if os_type == "windows" then + local source = gsub(source, "^%.+/", "") + local dest = gsub(dest, "^%.+/", "") + return execute("ren " .. unix_to_win(dir) .. source .. " " .. dest) + else + return execute("mv " .. dir .. source .. " " .. dir .. dest) + end +end + +-- Remove file(s) based on a glob +function rm(source, glob) + for _,i in ipairs(filelist(source, glob)) do + rmfile(source,i) + end + -- os.remove doesn't give a sensible errorlevel + return 0 +end + +-- Remove file +function rmfile(source, file) + remove(source .. "/" .. file) + -- os.remove doesn't give a sensible errorlevel + return 0 +end + +-- Remove a directory tree +function rmdir(dir) + -- First, make sure it exists to avoid any errors + mkdir(dir) + if os_type == "windows" then + return execute("rmdir /s /q " .. unix_to_win(dir)) + else + return execute("rm -r " .. dir) + end +end + +-- Run a command in a given directory +function run(dir, cmd) + return execute("cd " .. dir .. os_concat .. cmd) +end + +-- Split a path into file and directory component +function splitpath(file) + local path, name = match(file, "^(.*)/([^/]*)$") + if path then + return path, name + else + return ".", file + end +end + +-- Arguably clearer names +function basename(file) + return(select(2, splitpath(file))) +end + +function dirname(file) + return(select(1, splitpath(file))) +end + +-- Strip the extension from a file name (if present) +function jobname(file) + local name = match(basename(file), "^(.*)%.") + return name or file +end + +-- Look for files, directory by directory, and return the first existing +function locate(dirs, names) + for _,i in ipairs(dirs) do + for _,j in ipairs(names) do + local path = i .. "/" .. j + if fileexists(path) then + return path + end + end + end +end diff --git a/macros/latex/contrib/l3build/l3build-help.lua b/macros/latex/contrib/l3build/l3build-help.lua new file mode 100644 index 0000000000..8c3877595f --- /dev/null +++ b/macros/latex/contrib/l3build/l3build-help.lua @@ -0,0 +1,86 @@ +--[[ + +File l3build-help.lua Copyright (C) 2018 The LaTeX3 Project + +It may be distributed and/or modified under the conditions of the +LaTeX Project Public License (LPPL), either version 1.3c of this +license or (at your option) any later version. The latest version +of this license is in the file + + http://www.latex-project.org/lppl.txt + +This file is part of the "l3build bundle" (The Work in LPPL) +and all files in that bundle must be distributed together. + +----------------------------------------------------------------------- + +The development version of the bundle can be found at + + https://github.com/latex3/l3build + +for those people who are interested. + +--]] + +local insert = table.insert +local match = string.match +local rep = string.rep +local sort = table.sort + +function version() + print( + "\n" .. + "l3build: A testing and building system for LaTeX\n\n" .. + "Release " .. release_date + ) +end + +function help() + local function setup_list(list) + local longest = 0 + for k,v in pairs(list) do + if k:len() > longest then + longest = k:len() + end + end + -- Sort the options + local t = { } + for k,_ in pairs(list) do + insert(t, k) + end + sort(t) + return longest,t + end + + local scriptname = "l3build" + if not match(arg[0], "l3build(%.lua)$") then + scriptname = arg[0] + end + print("usage: " .. scriptname .. " [] []") + print("") + print("Valid commands are:") + local longest,t = setup_list(target_list) + for _,k in ipairs(t) do + local target = target_list[k] + local filler = rep(" ", longest - k:len() + 1) + if target["desc"] then + print(" " .. k .. filler .. target["desc"]) + end + end + print("") + print("Valid options are:") + local longest,t = setup_list(option_list) + for _,k in ipairs(t) do + local opt = option_list[k] + local filler = rep(" ", longest - k:len() + 1) + if opt["desc"] then + if opt["short"] then + print(" --" .. k .. "|-" .. opt["short"] .. filler .. opt["desc"]) + else + print(" --" .. k .. " " .. filler .. opt["desc"]) + end + end + end + print("") + print("See l3build.pdf for further details.") +end diff --git a/macros/latex/contrib/l3build/l3build-install.lua b/macros/latex/contrib/l3build/l3build-install.lua new file mode 100644 index 0000000000..d2222c8561 --- /dev/null +++ b/macros/latex/contrib/l3build/l3build-install.lua @@ -0,0 +1,268 @@ +--[[ + +File l3build-install.lua Copyright (C) 2018,2019 The LaTeX3 Project + +It may be distributed and/or modified under the conditions of the +LaTeX Project Public License (LPPL), either version 1.3c of this +license or (at your option) any later version. The latest version +of this license is in the file + + http://www.latex-project.org/lppl.txt + +This file is part of the "l3build bundle" (The Work in LPPL) +and all files in that bundle must be distributed together. + +----------------------------------------------------------------------- + +The development version of the bundle can be found at + + https://github.com/latex3/l3build + +for those people who are interested. + +--]] + +local ipairs = ipairs +local pairs = pairs +local print = print + +local set_program = kpse.set_program_name +local var_value = kpse.var_value + +local gsub = string.gsub +local lower = string.lower +local match = string.match + +local insert = table.insert + +local function gethome() + set_program("latex") + return abspath(options["texmfhome"] or var_value("TEXMFHOME")) +end + +function uninstall() + local function zapdir(dir) + local installdir = gethome() .. "/" .. dir + if options["dry-run"] then + local files = filelist(installdir) + if next(files) then + print("\n" .. "For removal from " .. installdir .. ":") + for _,file in pairs(filelist(installdir)) do + print("- " .. file) + end + end + return 0 + else + if direxists(installdir) then + return rmdir(installdir) + end + end + return 0 + end + local function uninstall_files(dir,subdir) + subdir = subdir or moduledir + dir = dir .. "/" .. subdir + return zapdir(dir) + end + local errorlevel = 0 + -- Any script man files need special handling + local manfiles = { } + for _,glob in pairs(scriptmanfiles) do + for file,_ in pairs(tree(docfiledir,glob)) do + -- Man files should have a single-digit extension: the type + local installdir = gethome() .. "/doc/man/man" .. match(file,".$") + if fileexists(installdir .. "/" .. file) then + if options["dry-run"] then + insert(manfiles,"man" .. match(file,".$") .. "/" .. + select(2,splitpath(file))) + else + errorlevel = errorlevel + rm(installdir,file) + end + end + end + end + if next(manfiles) then + print("\n" .. "For removal from " .. gethome() .. "/doc/man:") + for _,v in ipairs(manfiles) do + print("- " .. v) + end + end + errorlevel = uninstall_files("doc") + + uninstall_files("source") + + uninstall_files("tex") + + uninstall_files("bibtex/bst",module) + + uninstall_files("makeindex",module) + + uninstall_files("scripts",module) + + errorlevel + if errorlevel ~= 0 then return errorlevel end + -- Finally, clean up special locations + for _,location in ipairs(tdslocations) do + local path,glob = splitpath(location) + errorlevel = zapdir(path) + if errorlevel ~= 0 then return errorlevel end + end + return 0 +end + +function install_files(target,full,dry_run) + local function install_files(source,dir,files,subdir) + subdir = subdir or moduledir + -- For material associated with secondary tools (BibTeX, MakeIndex) + -- the structure needed is slightly different from those items going + -- into the tex/doc/source trees + if (dir == "makeindex" or match(dir,"$bibtex")) and module == "base" then + subdir = "latex" + end + dir = dir .. (subdir and ("/" .. subdir) or "") + local filenames = { } + local paths = { } + -- Generate a file list and include the directory + for _,glob_table in pairs(files) do + for _,glob in pairs(glob_table) do + for file,_ in pairs(tree(source,glob)) do + -- Just want the name + local file = gsub(file,"^%./","") + local matched = false + for _,location in ipairs(tdslocations) do + local path,glob = splitpath(location) + local pattern = glob_to_pattern(glob) + if match(file,pattern) then + insert(paths,path) + insert(filenames,path .. "/" .. file) + matched = true + break + end + end + if not matched then + insert(paths,dir) + insert(filenames,dir .. "/" .. file) + end + end + end + end + + local errorlevel = 0 + -- The target is only created if there are actual files to install + if next(filenames) then + if not dry_run then + for _,path in pairs(paths) do + errorlevel = cleandir(target .. "/" .. path) + if errorlevel ~= 0 then return errorlevel end + end + end + for _,file in ipairs(filenames) do + if dry_run then + print("- " .. file) + else + local path,file = splitpath(file) + errorlevel = cp(file,source,target .. "/" .. path) + if errorlevel ~= 0 then return errorlevel end + end + end + end + return 0 + end + local errorlevel = unpack() + if errorlevel ~= 0 then return errorlevel end + + -- Creates a 'controlled' list of files + local function excludelist(dir,include,exclude) + include = include or { } + exclude = exclude or { } + dir = dir or currentdir + local includelist = { } + local excludelist = { } + for _,glob_table in pairs(exclude) do + for _,glob in pairs(glob_table) do + for file,_ in pairs(tree(dir,glob)) do + excludelist[file] = true + end + end + end + for _,glob in pairs(include) do + for file,_ in pairs(tree(dir,glob)) do + if not excludelist[file] then + insert(includelist, file) + end + end + end + return includelist + end + + local installlist = excludelist(unpackdir,installfiles,{scriptfiles}) + + if full then + errorlevel = doc() + if errorlevel ~= 0 then return errorlevel end + -- For the purposes here, any typesetting demo files need to be + -- part of the main typesetting list + local typesetfiles = typesetfiles + for _,glob in pairs(typesetdemofiles) do + insert(typesetfiles,glob) + end + + -- Find PDF files + pdffiles = { } + for _,glob in pairs(typesetfiles) do + insert(pdffiles,(gsub(glob,"%.%w+$",".pdf"))) + end + + -- Set up lists: global as they are also needed to do CTAN releases + typesetlist = excludelist(docfiledir,typesetfiles,{sourcefiles}) + sourcelist = excludelist(sourcefiledir,sourcefiles, + {bstfiles,installfiles,makeindexfiles,scriptfiles}) + + if dry_run then + print("\nFor installation inside " .. target .. ":") + end + + errorlevel = install_files(sourcefiledir,"source",{sourcelist}) + + install_files(docfiledir,"doc", + {bibfiles,demofiles,docfiles,pdffiles,textfiles,typesetlist}) + if errorlevel ~= 0 then return errorlevel end + + -- Rename README if necessary + if not dry_run then + if ctanreadme ~= "" and not match(lower(ctanreadme),"^readme%.%w+") then + local installdir = target .. "/doc/" .. moduledir + if fileexists(installdir .. "/" .. ctanreadme) then + ren(installdir,ctanreadme,"README." .. match(ctanreadme,"%.(%w+)$")) + end + end + end + + -- Any script man files need special handling + local manfiles = { } + for _,glob in pairs(scriptmanfiles) do + for file,_ in pairs(tree(docfiledir,glob)) do + if dry_run then + insert(manfiles,"man" .. match(file,".$") .. "/" .. + select(2,splitpath(file))) + else + -- Man files should have a single-digit extension: the type + local installdir = target .. "/doc/man/man" .. match(file,".$") + errorlevel = errorlevel + mkdir(installdir) + errorlevel = errorlevel + cp(file,docfiledir,installdir) + end + end + end + if next(manfiles) then + for _,v in ipairs(manfiles) do + print("- doc/man/" .. v) + end + end + end + + if errorlevel ~= 0 then return errorlevel end + + errorlevel = install_files(unpackdir,"tex",{installlist}) + + install_files(unpackdir,"bibtex/bst",{bstfiles},module) + + install_files(unpackdir,"makeindex",{makeindexfiles},module) + + install_files(unpackdir,"scripts",{scriptfiles},module) + + return errorlevel +end + +function install() + return install_files(gethome(),options["full"],options["dry-run"]) +end diff --git a/macros/latex/contrib/l3build/l3build-manifest-setup.lua b/macros/latex/contrib/l3build/l3build-manifest-setup.lua new file mode 100644 index 0000000000..fb46eb1d36 --- /dev/null +++ b/macros/latex/contrib/l3build/l3build-manifest-setup.lua @@ -0,0 +1,364 @@ +--[[ + +File l3build-manifest-setup.lua Copyright (C) 2017-2018 The LaTeX3 Project + +It may be distributed and/or modified under the conditions of the +LaTeX Project Public License (LPPL), either version 1.3c of this +license or (at your option) any later version. The latest version +of this license is in the file + + http://www.latex-project.org/lppl.txt + +This file is part of the "l3build bundle" (The Work in LPPL) +and all files in that bundle must be distributed together. + +----------------------------------------------------------------------- + +The development version of the bundle can be found at + + https://github.com/latex3/l3build + +for those people who are interested. + +--]] + + +--[[ + L3BUILD MANIFEST SETUP + ====================== + This file contains all of the code that is easily replaceable by the user. + Either create a copy of this file, rename, and include alongside your `build.lua` + script and load it with `dofile()`, or simply copy/paste the definitions below + into your `build.lua` script directly. +--]] + + +--[[ + Setup of manifest "groups" + -------------------------- + + The grouping of manifest files is broken into three subheadings: + + * The development repository + * The TDS structure from `ctan` + * The CTAN structure from `ctan` + + The latter two will only be produced if the `manifest` target is run *after* + the `ctan` target. Contrarily, if you run `clean` before `manifest` then + only the first grouping will be printed. + + If you want to omit the files in the development repository, essentially + producing a minimalist manifest with only the files included for distribution, + make a copy of the `manifest_setup` function and delete the groups under + the ‘Repository manifest’ subheading below. +--]] + + +manifest_setup = manifest_setup or function() + local groups = { + { + subheading = "Repository manifest", + description = [[ +The following groups list the files included in the development repository of the package. +Files listed with a ‘†’ marker are included in the TDS but not CTAN files, and files listed +with ‘‡’ are included in both. +]], + }, + { + name = "Source files", + description = [[ +These are source files for a number of purposes, including the `unpack` process which +generates the installation files of the package. Additional files included here will also +be installed for processing such as testing. +]], + files = {sourcefiles}, + dir = sourcefiledir or maindir, -- TODO: remove "or maindir" after rebasing onto master + }, + { + name = "Typeset documentation source files", + description = [[ +These files are typeset using LaTeX to produce the PDF documentation for the package. +]], + files = {typesetfiles,typesetsourcefiles,typesetdemofiles}, + }, + { + name = "Documentation files", + description = [[ +These files form part of the documentation but are not typeset. Generally they will be +additional input files for the typeset documentation files listed above. +]], + files = {docfiles}, + dir = docfiledir or maindir, -- TODO: remove "or maindir" after rebasing onto master + }, + { + name = "Text files", + description = [[ +Plain text files included as documentation or metadata. +]], + files = {textfiles}, + skipfiledescription = true, + }, + { + name = "Demo files", + description = [[ +Files included to demonstrate package functionality. These files are *not* +typeset or compiled in any way. +]], + files = {demofiles}, + }, + { + name = "Bibliography and index files", + description = [[ +Supplementary files used for compiling package documentation. +]], + files = {bibfiles,bstfiles,makeindexfiles}, + }, + { + name = "Derived files", + description = [[ +The files created by ‘unpacking’ the package sources. This typically includes +`.sty` and `.cls` files created from DocStrip `.dtx` files. +]], + files = {installfiles}, + exclude = {excludefiles,sourcefiles}, + dir = unpackdir, + skipfiledescription = true, + }, + { + name = "Typeset documents", + description = [[ +The output files (PDF, essentially) from typesetting the various source, demo, +etc., package files. +]], + files = {typesetfiles,typesetsourcefiles,typesetdemofiles}, + rename = {"%.%w+$", ".pdf"}, + skipfiledescription = true, + }, + { + name = "Support files", + description = [[ +These files are used for unpacking, typesetting, or checking purposes. +]], + files = {unpacksuppfiles,typesetsuppfiles,checksuppfiles}, + dir = supportdir, + }, + { + name = "Checking-specific support files", + description = [[ +Support files for checking the test suite. +]], + files = {"*.*"}, + exclude = {{".",".."},excludefiles}, + dir = testsuppdir, + }, + { + name = "Test files", + description = [[ +These files form the test suite for the package. `.lvt` or `.lte` files are the individual +unit tests, and `.tlg` are the stored output for ensuring changes to the package produce +the same output. These output files are sometimes shared and sometime specific for +different engines (pdfTeX, XeTeX, LuaTeX, etc.). +]], + files = {"*"..lvtext,"*"..lveext,"*"..tlgext}, + dir = testfiledir, + skipfiledescription = true, + }, + { + subheading = "TDS manifest", + description = [[ +The following groups list the files included in the TeX Directory Structure used to install +the package into a TeX distribution. +]], + }, + { + name = "Source files (TDS)", + description = "All files included in the `"..module.."/source` directory.\n", + dir = tdsdir.."/source/"..moduledir, + files = {"*.*"}, + exclude = {".",".."}, + flag = false, + skipfiledescription = true, + }, + { + name = "TeX files (TDS)", + description = "All files included in the `"..module.."/tex` directory.\n", + dir = tdsdir.."/tex/"..moduledir, + files = {"*.*"}, + exclude = {".",".."}, + flag = false, + skipfiledescription = true, + }, + { + name = "Doc files (TDS)", + description = "All files included in the `"..module.."/doc` directory.\n", + dir = tdsdir.."/doc/"..moduledir, + files = {"*.*"}, + exclude = {".",".."}, + flag = false, + skipfiledescription = true, + }, + { + subheading = "CTAN manifest", + description = [[ +The following group lists the files included in the CTAN package. +]], + }, + { + name = "CTAN files", + dir = ctandir.."/"..module, + files = {"*.*"}, + exclude = {".",".."}, + flag = false, + skipfiledescription = true, + }, + } + return groups +end + +--[[ + Sorting within groups + --------------------- +--]] + +manifest_sort_within_match = manifest_sort_within_match or function(files) + local f = files + table.sort(f) + return f +end + +manifest_sort_within_group = manifest_sort_within_group or function(files) + local f = files + --[[ + -- no-op by default; make your own definition to customise. E.g.: + table.sort(f) + --]] + return f +end + +--[[ + Writing to file + --------------- +--]] + +manifest_write_opening = manifest_write_opening or function(filehandle) + + filehandle:write("# Manifest for " .. module .. "\n\n") + filehandle:write([[ +This file is a listing of all files considered to be part of this package. +It is automatically generated with `texlua build.lua manifest`. +]]) + +end + +manifest_write_subheading = manifest_write_subheading or function(filehandle,heading,description) + + filehandle:write("\n\n## " .. heading .. "\n\n") + + if description then + filehandle:write(description) + end + +end + +manifest_write_group_heading = manifest_write_group_heading or function (filehandle,heading,description) + + filehandle:write("\n### " .. heading .. "\n\n") + + if description then + filehandle:write(description .. "\n") + end + +end + +manifest_write_group_file = manifest_write_group_file or function(filehandle,filename,param) + --[[ + filehandle : write file object + filename : the count of the filename to be written + + param.dir : the directory of the file + param.count : the name of the file to write + param.filemaxchar : the maximum number of chars of all filenames in this group + param.flag : false OR string for indicating CTAN/TDS location + param.ctanfile : (boolean) if file is in CTAN dir + param.tdsfile : (boolean) if file is in TDS dir + --]] + + -- no file description: plain bullet list item: + + flagstr = param.flag or "" + filehandle:write("* " .. filename .. " " .. flagstr .. "\n") + + --[[ + -- or if you prefer an enumerated list: + filehandle:write(param.count..". " .. filename .. "\n") + --]] + + +end + +manifest_write_group_file_descr = manifest_write_group_file_descr or function(filehandle,filename,descr,param) + --[[ + filehandle : write file object + filename : the name of the file to write + descr : description of the file to write + + param.dir : the directory of the file + param.count : the count of the filename to be written + param.filemaxchar : the maximum number of chars of all filenames in this group + param.descmaxchar : the maximum number of chars of all descriptions in this group + param.flag : false OR string for indicating CTAN/TDS location + param.ctanfile : (boolean) if file is in CTAN dir + param.tdsfile : (boolean) if file is in TDS dir + --]] + + -- filename+description: Github-flavoured Markdown table + + filestr = string.format(" | %-"..param.filemaxchar.."s",filename) + flagstr = param.flag and string.format(" | %s",param.flag) or "" + descstr = string.format(" | %-"..param.descmaxchar.."s",descr) + + filehandle:write(filestr..flagstr..descstr.." |\n") + +end + +--[[ + Extracting ‘descriptions’ from source files + ------------------------------------------- +--]] + +manifest_extract_filedesc = manifest_extract_filedesc or function(filehandle) + + -- no-op by default; two examples below + +end + +--[[ + +-- From the first match of a pattern in a file: +manifest_extract_filedesc = function(filehandle) + + local all_file = filehandle:read("*all") + local matchstr = "\\section{(.-)}" + + filedesc = string.match(all_file,matchstr) + + return filedesc +end + +-- From the match of the 2nd line (say) of a file: +manifest_extract_filedesc = function(filehandle) + + local end_read_loop = 2 + local matchstr = "%%%S%s+(.*)" + local this_line = "" + + for ii = 1, end_read_loop do + this_line = filehandle:read("*line") + end + + filedesc = string.match(this_line,matchstr) + + return filedesc +end + +]]-- diff --git a/macros/latex/contrib/l3build/l3build-manifest.lua b/macros/latex/contrib/l3build/l3build-manifest.lua new file mode 100644 index 0000000000..3b899d55fd --- /dev/null +++ b/macros/latex/contrib/l3build/l3build-manifest.lua @@ -0,0 +1,280 @@ +--[[ + +File l3build-manifest.lua Copyright (C) 2017-2018 The LaTeX3 Project + +It may be distributed and/or modified under the conditions of the +LaTeX Project Public License (LPPL), either version 1.3c of this +license or (at your option) any later version. The latest version +of this license is in the file + + http://www.latex-project.org/lppl.txt + +This file is part of the "l3build bundle" (The Work in LPPL) +and all files in that bundle must be distributed together. + +----------------------------------------------------------------------- + +The development version of the bundle can be found at + + https://github.com/latex3/l3build + +for those people who are interested. + +--]] + + +--[[ + L3BUILD MANIFEST + ================ + If desired this entire function can be replaced; if not, it uses a number of + auxiliary functions which are included in this file. + + Additional setup can be performed by replacing the functions lists in the file + `l3build-manifest-setup.lua`. +--]] + +manifest = manifest or function() + + -- build list of ctan files + ctanfiles = {} + for _,f in ipairs(filelist(ctandir.."/"..ctanpkg,"*.*")) do + ctanfiles[f] = true + end + tdsfiles = {} + for _,subdir in ipairs({"/doc/","/source/","/tex/"}) do + for _,f in ipairs(filelist(tdsdir..subdir..moduledir,"*.*")) do + tdsfiles[f] = true + end + end + + local manifest_entries = manifest_setup() + + for ii,_ in ipairs(manifest_entries) do + manifest_entries[ii] = manifest_build_list(manifest_entries[ii]) + end + + manifest_write(manifest_entries) + + printline = "Manifest written to " .. manifestfile + print((printline:gsub(".","*"))) print(printline) print((printline:gsub(".","*"))) + +end + +--[[ + Internal Manifest functions: build_list + --------------------------------------- +--]] + +manifest_build_list = function(entry) + + if not(entry.subheading) then + + entry = manifest_build_init(entry) + + -- build list of excluded files + for _,glob_list in ipairs(entry.exclude) do + for _,this_glob in ipairs(glob_list) do + for _,this_file in ipairs(filelist(maindir,this_glob)) do + entry.excludes[this_file] = true + end + end + end + + -- build list of matched files + for _,glob_list in ipairs(entry.files) do + for _,this_glob in ipairs(glob_list) do + + local these_files = filelist(entry.dir,this_glob) + these_files = manifest_sort_within_match(these_files) + + for _,this_file in ipairs(these_files) do + entry = manifest_build_file(entry,this_file) + end + + entry.files_ordered = manifest_sort_within_group(entry.files_ordered) + + end + end + + end + + return entry + +end + + +manifest_build_init = function(entry) + + -- currently these aren't customisable; I guess they could be? + local manifest_group_defaults = { + skipfiledescription = false , + rename = false , + dir = maindir , + exclude = {excludefiles} , + flag = true , + } + + -- internal data added to each group in the table that needs to be initialised + local manifest_group_init = { + N = 0 , -- # matched files + ND = 0 , -- # descriptions + matches = {} , + excludes = {} , + files_ordered = {} , + descr = {} , + Nchar_file = 4 , -- TODO: generalise + Nchar_descr = 11 , -- TODO: generalise + } + + -- copy default options to each group if necessary + for kk,ll in pairs(manifest_group_defaults) do + if entry[kk] == nil then + entry[kk] = ll + end + -- can't use "entry[kk] = entry[kk] or ll" because false/nil are indistinguishable! + end + + -- initialisation for internal data + for kk,ll in pairs(manifest_group_init) do + entry[kk] = ll + end + + -- allow nested tables by requiring two levels of nesting + if type(entry.files[1])=="string" then + entry.files = {entry.files} + end + if type(entry.exclude[1])=="string" then + entry.exclude = {entry.exclude} + end + + return entry + +end + + +manifest_build_file = function(entry,this_file) + + if entry.rename then + this_file = this_file:gsub(entry.rename[1],entry.rename[2]) + end + + if not entry.excludes[this_file] then + + entry.N = entry.N+1 + if not(entry.matches[this_file]) then + + entry.matches[this_file] = true -- store the file name + entry.files_ordered[entry.N] = this_file -- store the file order + entry.Nchar_file = math.max(entry.Nchar_file,this_file:len()) + + end + + if not(entry.skipfiledescription) then + + local ff = assert(io.open(entry.dir .. "/" .. this_file, "r")) + this_descr = manifest_extract_filedesc(ff,this_file) + ff:close() + + if this_descr and this_descr ~= "" then + entry.descr[this_file] = this_descr + entry.ND = entry.ND+1 + entry.Nchar_descr = math.max(entry.Nchar_descr,this_descr:len()) + end + + end + end + + return entry + +end + +--[[ + Internal Manifest functions: write + ---------------------------------- +--]] + +manifest_write = function(manifest_entries) + + local f = assert(io.open(manifestfile, "w")) + manifest_write_opening(f) + + for ii,vv in ipairs(manifest_entries) do + if manifest_entries[ii].subheading then + manifest_write_subheading(f,manifest_entries[ii].subheading,manifest_entries[ii].description) + elseif manifest_entries[ii].N > 0 then + manifest_write_group(f,manifest_entries[ii]) + end + end + + f:close() + +end + + +manifest_write_group = function(f,entry) + + manifest_write_group_heading(f,entry.name,entry.description) + + if entry.ND > 0 then + + for ii,file in ipairs(entry.files_ordered) do + local descr = entry.descr[file] or "" + local param = { + dir = entry.dir , + count = ii , + filemaxchar = entry.Nchar_file , + descmaxchar = entry.Nchar_descr , + ctanfile = ctanfiles[file] , + tdsfile = tdsfiles[file] , + flag = false , + } + + if entry.flag then + param.flag = " " + if tdsfiles[file] and not(ctanfiles[file]) then + param.flag = "† " + elseif ctanfiles[file] then + param.flag = "‡ " + end + end + + if ii == 1 then + -- header of table + -- TODO: generalise + local p = {} + for k,v in pairs(param) do p[k] = v end + p.count = -1 + p.flag = p.flag and "Flag" + manifest_write_group_file_descr(f,"File","Description",p) + p.flag = p.flag and "--- " + manifest_write_group_file_descr(f,"---","---",p) + end + + manifest_write_group_file_descr(f,file,descr,param) + end + + else + + for ii,file in ipairs(entry.files_ordered) do + local param = { + dir = entry.dir , + count = ii , + filemaxchar = entry.Nchar_file , + ctanfile = ctanfiles[file] , + tdsfile = tdsfiles[file] , + } + if entry.flag then + param.flag = "" + if tdsfiles[file] and not(ctanfiles[file]) then + param.flag = "†" + elseif ctanfiles[file] then + param.flag = "‡" + end + end + manifest_write_group_file(f,file,param) + end + + end + +end + diff --git a/macros/latex/contrib/l3build/l3build-stdmain.lua b/macros/latex/contrib/l3build/l3build-stdmain.lua new file mode 100644 index 0000000000..668b2d02d0 --- /dev/null +++ b/macros/latex/contrib/l3build/l3build-stdmain.lua @@ -0,0 +1,185 @@ +--[[ + +File l3build-stdmain.lua Copyright (C) 2018 The LaTeX3 Project + +It may be distributed and/or modified under the conditions of the +LaTeX Project Public License (LPPL), either version 1.3c of this +license or (at your option) any later version. The latest version +of this license is in the file + + http://www.latex-project.org/lppl.txt + +This file is part of the "l3build bundle" (The Work in LPPL) +and all files in that bundle must be distributed together. + +----------------------------------------------------------------------- + +The development version of the bundle can be found at + + https://github.com/latex3/l3build + +for those people who are interested. + +--]] + +local exit = os.exit +local insert = table.insert + +-- List all modules +function listmodules() + local modules = { } + local exclmodules = exclmodules or { } + for entry in lfs.dir(".") do + if entry ~= "." and entry ~= ".." then + local attr = lfs.attributes(entry) + assert(type(attr) == "table") + if attr.mode == "directory" then + if not exclmodules[entry] then + insert(modules, entry) + end + end + end + end + return modules +end + +target_list = + { + -- Some hidden targets + bundlecheck = + { + func = check, + pre = function() + if names then + print("Bundle checks should not list test names") + help() + exit(1) + end + return 0 + end + }, + bundlectan = + { + func = bundlectan + }, + bundleunpack = + { + func = bundleunpack, + pre = function() return(depinstall(unpackdeps)) end + }, + -- Public targets + check = + { + bundle_target = true, + desc = "Run all automated tests", + func = check, + }, + clean = + { + bundle_func = bundleclean, + desc = "Clean out directory tree", + func = clean + }, + ctan = + { + bundle_func = ctan, + desc = "Create CTAN-ready archive", + func = ctan + }, + doc = + { + desc = "Typesets all documentation files", + func = doc + }, + install = + { + desc = "Installs files into the local textmf tree", + func = install + }, + manifest = + { + desc = "Creates a manifest file", + func = manifest + }, + save = + { + desc = "Saves test validation log", + func = save + }, + tag = + { + bundle_func = function(names) + local modules = modules or listmodules() + local errorlevel = call(modules,"tag") + -- Deal with any files in the bundle dir itself + if errorlevel == 0 then + errorlevel = tag(names) + end + return errorlevel + end, + desc = "Updates release tags in files", + func = tag, + pre = function(names) + if names and #names > 1 then + print("Too many tags specified; exactly one required") + exit(1) + end + return 0 + end + }, + uninstall = + { + desc = "Uninstalls files from the local textmf tree", + func = uninstall + }, + unpack= + { + bundle_target = true, + desc = "Unpacks the source files into the build tree", + func = unpack + }, + upload = + { + desc = "Send archive to CTAN for public release", + func = upload + }, + } + +-- +-- The overall main function +-- + +function stdmain(target,names) + -- Deal with unknown targets up-front + if not target_list[target] then + help() + exit(1) + end + local errorlevel = 0 + if module == "" then + modules = modules or listmodules() + if target_list[target].bundle_func then + errorlevel = target_list[target].bundle_func(names) + else + -- Detect all of the modules + if target_list[target].bundle_target then + target = "bundle" .. target + end + errorlevel = call(modules,target) + end + else + if target_list[target].pre then + errorlevel = target_list[target].pre(names) + if errorlevel ~= 0 then + exit(1) + end + end + errorlevel = target_list[target].func(names) + end + -- All done, finish up + if errorlevel ~= 0 then + exit(1) + else + exit(0) + end +end diff --git a/macros/latex/contrib/l3build/l3build-tagging.lua b/macros/latex/contrib/l3build/l3build-tagging.lua new file mode 100644 index 0000000000..2c0c1641a4 --- /dev/null +++ b/macros/latex/contrib/l3build/l3build-tagging.lua @@ -0,0 +1,84 @@ +--[[ + +File l3build-tagging.lua Copyright (C) 2018 The LaTeX3 Project + +It may be distributed and/or modified under the conditions of the +LaTeX Project Public License (LPPL), either version 1.3c of this +license or (at your option) any later version. The latest version +of this license is in the file + + http://www.latex-project.org/lppl.txt + +This file is part of the "l3build bundle" (The Work in LPPL) +and all files in that bundle must be distributed together. + +----------------------------------------------------------------------- + +The development version of the bundle can be found at + + https://github.com/latex3/l3build + +for those people who are interested. + +--]] + +local pairs = pairs +local open = io.open +local os_date = os.date +local match = string.match +local gsub = string.gsub + +update_tag = update_tag or function(filename,content,tagname,tagdate) + return content +end + +function tag_hook(tagname,tagdate) + return 0 +end + +local function update_file_tag(file,tagname,tagdate) + local filename = basename(file) + print("Tagging ".. filename) + local f = assert(open(file,"rb")) + local content = f:read("*all") + f:close() + -- Deal with Unix/Windows line endings + content = gsub(content .. (match(content,"\n$") and "" or "\n"), + "\r\n", "\n") + local updated_content = update_tag(filename,content,tagname,tagdate) + if content == updated_content then + return 0 + else + local path = dirname(file) + ren(path,filename,filename .. ".bak") + local f = assert(open(file,"w")) + -- Convert line ends back if required during write + -- Watch for the second return value! + f:write((gsub(updated_content,"\n",os_newline))) + f:close() + rm(path,filename .. ".bak") + end + return 0 +end + +function tag(tagnames) + local tagdate = options["date"] or os_date("%Y-%m-%d") + local tagname = nil + if tagnames then + tagname = tagnames[1] + end + local dirs = remove_duplicates({currentdir, sourcefiledir, docfiledir}) + local errorlevel = 0 + for _,dir in pairs(dirs) do + for _,filetype in pairs(tagfiles) do + for file,_ in pairs(tree(dir,filetype)) do + errorlevel = update_file_tag(dir .. "/" .. file,tagname,tagdate) + if errorlevel ~= 0 then + return errorlevel + end + end + end + end + return tag_hook(tagname,tagdate) +end + diff --git a/macros/latex/contrib/l3build/l3build-typesetting.lua b/macros/latex/contrib/l3build/l3build-typesetting.lua new file mode 100644 index 0000000000..eb2974cdf2 --- /dev/null +++ b/macros/latex/contrib/l3build/l3build-typesetting.lua @@ -0,0 +1,233 @@ +--[[ + +File l3build-typesetting.lua Copyright (C) 2018,2019 The LaTeX3 Project + +It may be distributed and/or modified under the conditions of the +LaTeX Project Public License (LPPL), either version 1.3c of this +license or (at your option) any later version. The latest version +of this license is in the file + + http://www.latex-project.org/lppl.txt + +This file is part of the "l3build bundle" (The Work in LPPL) +and all files in that bundle must be distributed together. + +----------------------------------------------------------------------- + +The development version of the bundle can be found at + + https://github.com/latex3/l3build + +for those people who are interested. + +--]] + +-- +-- Auxiliary functions for typesetting: need to be generally available +-- + +local ipairs = ipairs +local pairs = pairs +local print = print + +local gsub = string.gsub +local match = string.match + +local os_type = os.type + +function dvitopdf(name, dir, engine, hide) + if match(engine, "^u?ptex$") then + run( + dir, + (forcecheckepoch and setepoch() or "") .. + "dvipdfmx " .. name .. dviext + .. (hide and (" > " .. os_null) or "") + ) + else + run( + dir, + (forcecheckepoch and setepoch() or "") .. + "dvips " .. name .. dviext + .. (hide and (" > " .. os_null) or "") + .. os_concat .. + "ps2pdf " .. name .. psext + .. (hide and (" > " .. os_null) or "") + ) + end +end + +-- An auxiliary used to set up the environmental variables +function runcmd(cmd,dir,vars) + local dir = dir or "." + local dir = abspath(dir) + local vars = vars or {} + -- Allow for local texmf files + local env = os_setenv .. " TEXMFCNF=." .. os_pathsep + local envpaths = "." .. os_pathsep + .. abspath(localdir) .. os_pathsep + .. dir .. (typesetsearch and os_pathsep or "") + -- Deal with spaces in paths + if os_type == "windows" and match(envpaths," ") then + envpaths = gsub(envpaths,'"','') + end + for _,var in pairs(vars) do + env = env .. os_concat .. os_setenv .. " " .. var .. "=" .. envpaths + end + return run(dir,(forcedocepoch and setepoch() or "") .. env .. os_concat .. cmd) +end + +function biber(name,dir) + if fileexists(dir .. "/" .. name .. ".bcf") then + return + runcmd(biberexe .. " " .. biberopts .. " " .. name,dir,{"BIBINPUTS"}) + end + return 0 +end + +function bibtex(name,dir) + local dir = dir or "." + if fileexists(dir .. "/" .. name .. ".aux") then + -- LaTeX always generates an .aux file, so there is a need to + -- look inside it for a \citation line + local grep + if os_type == "windows" then + grep = "\\\\" + else + grep = "\\\\\\\\" + end + if run(dir, + os_grepexe .. " \"^" .. grep .. "citation{\" " .. name .. ".aux > " + .. os_null + ) + run(dir, + os_grepexe .. " \"^" .. grep .. "bibdata{\" " .. name .. ".aux > " + .. os_null + ) == 0 then + return runcmd(bibtexexe .. " " .. bibtexopts .. " " .. name,dir, + {"BIBINPUTS","BSTINPUTS"}) + end + end + return 0 +end + +function makeindex(name,dir,inext,outext,logext,style) + local dir = dir or "." + if fileexists(dir .. "/" .. name .. inext) then + if style == "" then style = nil end + return runcmd(makeindexexe .. " " .. makeindexopts + .. " -o " .. name .. outext + .. (style and (" -s " .. style) or "") + .. " -t " .. name .. logext .. " " .. name .. inext, + dir, + {"INDEXSTYLE"}) + end + return 0 +end + +function tex(file,dir) + local dir = dir or "." + return runcmd(typesetexe .. " " .. typesetopts .. " \"" .. typesetcmds + .. "\\input " .. file .. "\"", + dir,{"TEXINPUTS","LUAINPUTS"}) +end + +local function typesetpdf(file,dir) + local dir = dir or "." + local name = jobname(file) + print("Typesetting " .. name) + local errorlevel = typeset(file,dir) + if errorlevel ~= 0 then + print(" ! Compilation failed") + return errorlevel + end + pdfname = name .. pdfext + rm(docfiledir,pdfname) + return cp(pdfname,dir,docfiledir) +end + +typeset = typeset or function(file,dir) + dir = dir or "." + local errorlevel = tex(file,dir) + if errorlevel ~= 0 then + return errorlevel + end + local name = jobname(file) + errorlevel = biber(name,dir) + bibtex(name,dir) + if errorlevel ~= 0 then + return errorlevel + end + for i = 2,typesetruns do + errorlevel = + makeindex(name,dir,".glo",".gls",".glg",glossarystyle) + + makeindex(name,dir,".idx",".ind",".ilg",indexstyle) + + tex(file,dir) + if errorlevel ~= 0 then break end + end + return errorlevel +end + +-- A hook to allow additional typesetting of demos +typeset_demo_tasks = typeset_demo_tasks or function() + return 0 +end + +-- Typeset all required documents +-- Uses a set of dedicated auxiliaries that need to be available to others +function doc(files) + -- Set up + cleandir(typesetdir) + for _,filetype in pairs( + {bibfiles, docfiles, typesetfiles, typesetdemofiles} + ) do + for _,file in pairs(filetype) do + cp(file, docfiledir, typesetdir) + end + end + for _,file in pairs(sourcefiles) do + cp(file, sourcefiledir, typesetdir) + end + for _,file in pairs(typesetsuppfiles) do + cp(file, supportdir, typesetdir) + end + depinstall(typesetdeps) + unpack({sourcefiles, typesetsourcefiles}, {sourcefiledir, docfiledir}) + -- Main loop for doc creation + local errorlevel = typeset_demo_tasks() + if errorlevel ~= 0 then + return errorlevel + end + local done = {} + for _,typesetfiles in ipairs({typesetdemofiles,typesetfiles}) do + for _,glob in pairs(typesetfiles) do + for _,dir in ipairs({typesetdir,unpackdir}) do + for _,file in pairs(tree(dir,glob)) do + local path,srcname = splitpath(file) + local name = jobname(srcname) + if not done[name] then + local typeset = true + -- Allow for command line selection of files + if files and next(files) then + typeset = false + for _,file in pairs(files) do + if name == file then + typeset = true + break + end + end + end + -- Now know if we should typeset this source + if typeset then + local errorlevel = typesetpdf(srcname,path) + if errorlevel ~= 0 then + return errorlevel + else + done[name] = true + end + end + end + end + end + end + end + return 0 +end + diff --git a/macros/latex/contrib/l3build/l3build-unpack.lua b/macros/latex/contrib/l3build/l3build-unpack.lua new file mode 100644 index 0000000000..89bbfaa17f --- /dev/null +++ b/macros/latex/contrib/l3build/l3build-unpack.lua @@ -0,0 +1,102 @@ +--[[ + +File l3build-unpack.lua Copyright (C) 2018 The LaTeX3 Project + +It may be distributed and/or modified under the conditions of the +LaTeX Project Public License (LPPL), either version 1.3c of this +license or (at your option) any later version. The latest version +of this license is in the file + + http://www.latex-project.org/lppl.txt + +This file is part of the "l3build bundle" (The Work in LPPL) +and all files in that bundle must be distributed together. + +----------------------------------------------------------------------- + +The development version of the bundle can be found at + + https://github.com/latex3/l3build + +for those people who are interested. + +--]] + +local execute = os.execute + +-- Unpack the package files using an 'isolated' system: this requires +-- a copy of the 'basic' DocStrip program, which is used then removed +function unpack(sources, sourcedirs) + local errorlevel = depinstall(unpackdeps) + if errorlevel ~= 0 then + return errorlevel + end + errorlevel = bundleunpack(sourcedirs, sources) + if errorlevel ~= 0 then + return errorlevel + end + for _,i in ipairs(installfiles) do + errorlevel = cp(i, unpackdir, localdir) + if errorlevel ~= 0 then + return errorlevel + end + end + return 0 +end + +-- Split off from the main unpack so it can be used on a bundle and not +-- leave only one modules files +bundleunpack = bundleunpack or function(sourcedirs, sources) + local errorlevel = mkdir(localdir) + if errorlevel ~=0 then + return errorlevel + end + errorlevel = cleandir(unpackdir) + if errorlevel ~=0 then + return errorlevel + end + for _,i in ipairs(sourcedirs or {sourcefiledir}) do + for _,j in ipairs(sources or {sourcefiles}) do + for _,k in ipairs(j) do + errorlevel = cp(k, i, unpackdir) + if errorlevel ~=0 then + return errorlevel + end + end + end + end + for _,i in ipairs(unpacksuppfiles) do + errorlevel = cp(i, supportdir, localdir) + if errorlevel ~=0 then + return errorlevel + end + end + for _,i in ipairs(unpackfiles) do + for j,_ in pairs(tree(unpackdir, i)) do + -- This 'yes' business is needed to pass a series of "y\n" to + -- TeX if \askforoverwrite is true + -- That is all done using a file as it's the only way on Windows and + -- on Unix the "yes" command can't be used inside execute (it never + -- stops, which confuses Lua) + execute(os_yes .. ">>" .. localdir .. "/yes") + local path, name = splitpath(j) + local localdir = abspath(localdir) + errorlevel = run( + unpackdir .. "/" .. path, + os_setenv .. " TEXINPUTS=." .. os_pathsep + .. localdir .. (unpacksearch and os_pathsep or "") .. + os_concat .. + os_setenv .. " LUAINPUTS=." .. os_pathsep + .. localdir .. (unpacksearch and os_pathsep or "") .. + os_concat .. + unpackexe .. " " .. unpackopts .. " " .. name .. " < " + .. localdir .. "/yes" + .. (options["quiet"] and (" > " .. os_null) or "") + ) + if errorlevel ~=0 then + return errorlevel + end + end + end + return 0 +end diff --git a/macros/latex/contrib/l3build/l3build-upload.lua b/macros/latex/contrib/l3build/l3build-upload.lua new file mode 100644 index 0000000000..de1899ad2c --- /dev/null +++ b/macros/latex/contrib/l3build/l3build-upload.lua @@ -0,0 +1,350 @@ +--[[ + +File l3build-upload.lua Copyright (C) 2018 The LaTeX3 Project + +It may be distributed and/or modified under the conditions of the +LaTeX Project Public License (LPPL), either version 1.3c of this +license or (at your option) any later version. The latest version +of this license is in the file + + http://www.latex-project.org/lppl.txt + +This file is part of the "l3build bundle" (The Work in LPPL) +and all files in that bundle must be distributed together. + +----------------------------------------------------------------------- + +The development version of the bundle can be found at + + https://github.com/latex3/l3build + +for those people who are interested. + +--]] + +local pairs = pairs +local print = print +local tostring = tostring + +local close = io.close +local flush = io.flush +local open = io.open +local output = io.output +local popen = io.popen +local read = io.read +local write = io.write + +local os_type = os.type + +local len = string.len +local lower = string.lower +local match = string.match + +-- UPLOAD() +-- +-- takes a package configuration table and an optional boolean +-- +-- if the upload parameter is not supplied or is not true, only package validation +-- is used, if upload is true then package upload will be attempted if validation +-- succeeds. + +-- fields are given as a string, or optionally for fields allowing multiple +-- values, as a table of strings. + +-- Mandatory fields are checked in Lua +-- Maximum string lengths are checked. + +-- Currently string values are not checked, eg licence names, or URL syntax. + +-- The input form could be used to construct a post body but +-- luasec is not included in texlua. Instead an external program is used to post. +-- As Windows (since April 2018) includes curl now use curl. +-- A version using ctan-o-mat is available in the ctan-post github repo. + +-- the main interface is +-- upload() +-- with a configuration table `uploadconfig` + + +local curl_debug = curl_debug or false -- to disable posting +-- For now, this is undocumented. + +local ctanupload = ctanupload or "ask" +if options["dry-run"] then + ctanupload = false +end +-- if ctanupload is nil or false, only validation is attempted +-- if ctanupload is true the ctan upload URL will be used after validation +-- if upload is anything else, the user will be prompted whether to upload. +-- For now, this is undocumented. I think I would prefer to keep it always set to ask for the time being. + +function upload(tagnames) + + local uploadfile = ctanzip..".zip" + + -- Keep data local + local uploadconfig = uploadconfig + + -- try a sensible default for the package name: + uploadconfig.pkg = uploadconfig.pkg or ctanpkg or nil + + -- Get data from command line if appropriate + if options["file"] then + local f = open(options["file"],"r") + uploadconfig.announcement = assert(f:read('*a')) + close(f) + end + uploadconfig.announcement = options["message"] or uploadconfig.announcement or file_contents(uploadconfig.announcement_file) + uploadconfig.email = options["email"] or uploadconfig.email + + + uploadconfig.note = uploadconfig.note or file_contents(uploadconfig.note_file) + + local tagnames = tagnames or { } + uploadconfig.version = tagnames[1] or uploadconfig.version + + local override_update_check = false + if uploadconfig.update == nil then + uploadconfig.update = true + override_update_check = true + end + + -- avoid lower level error from post command if zip file missing + local zip=open(trim_space(tostring(uploadfile)),"r") + if zip~=nil then + close(zip) + else + error("Missing zip file '" .. tostring(uploadfile) .. "'") + end + + ctan_post = construct_ctan_post(uploadfile,options["debug"]) + + +-- curl file version + local curloptfile = uploadconfig.curlopt_file or (ctanzip .. ".curlopt") + local curlopt=open(curloptfile,"w") + output(curlopt) + write(ctan_post) + close(curlopt) + + ctan_post=curlexe .. " --config " .. curloptfile + + +if options["debug"] then + ctan_post = ctan_post .. ' https://httpbin.org/post' + fp_return = shell(ctan_post) + print('\n\nCURL COMMAND:') + print(ctan_post) + print("\n\nHTTP RESPONSE:") + print(fp_return) + return 1 +else + ctan_post = ctan_post .. ' https://ctan.org/submit/' +end + + -- call post command to validate the upload at CTAN's validate URL + local exit_status=0 + local fp_return="" + + -- use popen not execute so get the return body local exit_status=os.execute(ctan_post .. "validate") + if (curl_debug==false) then + print("Contacting CTAN for validation:") + fp_return = shell(ctan_post .. "validate") + else + fp_return="WARNING: curl_debug==true: posting disabled" + print(ctan_post) + return 1 + end + if override_update_check then + if match(fp_return,"non%-existent%spackage") then + print("Package not found on CTAN; re-validating as new package:") + uploadconfig.update = false + ctan_post = construct_ctan_post(uploadfile) + fp_return = shell(ctan_post .. "validate") + end + end + if match(fp_return,"WARNING") or match(fp_return,"ERROR") then + exit_status=1 + end + + -- if upload requested and validation succeeded repost to the upload URL + if (exit_status==0 or exit_status==nil) then + if (ctanupload ~=nil and ctanupload ~=false and ctanupload ~= true) then + print("Validation successful, do you want to upload to CTAN? [y/n]" ) + local answer="" + io.stdout:write("> ") + io.stdout:flush() + answer=read() + if(lower(answer,1,1)=="y") then + ctanupload=true + end + end + if (ctanupload==true) then + fp_return = shell(ctan_post .. "upload") +-- this is just html, could save to a file +-- or echo a cleaned up version + print('Response from CTAN:') + print(fp_return) + if match(fp_return,"WARNING") or match(fp_return,"ERROR") then + exit_status=1 + end + else + print("CTAN validation successful") + end + else + error("Warnings from CTAN package validation:\n" .. fp_return) + end + return exit_status +end + + +function trim_space(s) + return (s:gsub("^%s*(.-)%s*$", "%1")) +end + + +function shell(s) + local h = assert(popen(s, 'r')) + t = assert(h:read('*a')) + h:close() + return t +end + +function construct_ctan_post(uploadfile,debug) + + -- start building the curl command: +-- commandline ctan_post = curlexe .. " " + ctan_post="" + + -- build up the curl command field-by-field: + + -- field max desc mandatory multi + -- ---------------------------------------------------------------------------------------------------- + ctan_field("announcement", uploadconfig.announcement, 8192, "Announcement", true, false ) + ctan_field("author", uploadconfig.author, 128, "Author name", true, false ) + ctan_field("bugtracker", uploadconfig.bugtracker, 255, "URL(s) of bug tracker", false, true ) + ctan_field("ctanPath", uploadconfig.ctanPath, 255, "CTAN path", true, false ) + ctan_field("description", uploadconfig.description, 4096, "Short description of package", false, false ) + ctan_field("development", uploadconfig.development, 255, "URL(s) of development channels", false, true ) + ctan_field("email", uploadconfig.email, 255, "Email of uploader", true, false ) + ctan_field("home", uploadconfig.home, 255, "URL(s) of home page", false, true ) + ctan_field("license", uploadconfig.license, 2048, "Package license(s)", true, true ) + ctan_field("note", uploadconfig.note, 4096, "Internal note to ctan", false, false ) + ctan_field("pkg", uploadconfig.pkg, 32, "Package name", true, false ) + ctan_field("repository", uploadconfig.repository, 255, "URL(s) of source repositories", false, true ) + ctan_field("summary", uploadconfig.summary, 128, "One-line summary of package", true, false ) + ctan_field("support", uploadconfig.support, 255, "URL(s) of support channels", false, true ) + ctan_field("topic", uploadconfig.topic, 1024, "Topic(s)", false, true ) + ctan_field("update", uploadconfig.update, 8, "Boolean: true=update, false=new pkg", false, false ) + ctan_field("uploader", uploadconfig.uploader, 255, "Name of uploader", true, false ) + ctan_field("version", uploadconfig.version, 32, "Package version", true, false ) + + -- finish constructing the curl command: + local qq = '"' + if os_type == "windows" then + qq = '\"' + end +-- commandline ctan_post = ctan_post .. ' --form ' .. qq .. 'file=@' .. tostring(uploadfile) .. ';filename=' .. tostring(uploadfile) .. qq + ctan_post = ctan_post .. '\nform="file=@' .. tostring(uploadfile) .. ';filename=' .. tostring(uploadfile) .. '"' + + return ctan_post + +end + +function ctan_field(fname,fvalue,max,desc,mandatory,multi) + if (type(fvalue)=="table" and multi==true) then + for i, v in pairs(fvalue) do + ctan_single_field(fname,v,max,desc,mandatory and i==1) + end + else + ctan_single_field(fname,fvalue,max,desc,mandatory) + end +end + + +function ctan_single_field(fname,fvalue,max,desc,mandatory) + local fvalueprint = fvalue + if fvalue == nil then fvalueprint = '??' end + print('ctan-upload | ' .. fname .. ': ' ..tostring(fvalueprint)) + if ((fvalue==nil and mandatory) or (fvalue == 'ask')) then + if (max < 256) then + fvalue=input_single_line_field(fname) + else + fvalue=input_multi_line_field(fname) + end + end + if (fvalue==nil or type(fvalue)~="table") then + local vs=trim_space(tostring(fvalue)) + if (mandatory==true and (fvalue == nil or vs=="")) then + error("The field " .. fname .. " must contain " .. desc) + end + if (fvalue ~=nil and len(vs) > 0) then + if (max > 0 and len(vs) > max) then + error("The field " .. fname .. " is longer than " .. max) + end + vs = vs:gsub('"','\\"') + vs = vs:gsub('`','\\`') + vs = vs:gsub('\n','\\n') +-- for strings on commandline version ctan_post=ctan_post .. ' --form "' .. fname .. "=" .. vs .. '"' + ctan_post=ctan_post .. '\nform="' .. fname .. '=' .. vs .. '"' + end + else + error("The value of the field '" .. fname .."' must be a scalar not a table") + end +end + + +-- function for interactive multiline fields +function input_multi_line_field (name) + print("Enter " .. name .. " three or ctrl-D to stop") + + local field="" + + local answer_line + local return_count=0 + repeat + write("> ") + flush() + answer_line=read() + if answer_line=="" then + return_count=return_count+1 + else + for i=1,return_count,1 do + field = field .. "\n" + end + return_count=0 + if answer_line~=nil then + field = field .. "\n" .. answer_line + end + end + until (return_count==3 or answer_line==nil or answer_line=='\004') + return field +end + +function input_single_line_field(name) + print("Enter " .. name ) + + local field="" + + write("> ") + flush() + field=read() + return field +end + + +-- if filename is non nil and file readable return contents otherwise nil +function file_contents (filename) + if filename ~= nil then + local f= open(filename,"r") + if f==nil then + return nil + else + local s = f:read("*all") + close(f) + return s + end + else + return nil + end +end diff --git a/macros/latex/contrib/l3build/l3build-variables.lua b/macros/latex/contrib/l3build/l3build-variables.lua new file mode 100644 index 0000000000..0b3c9a2e43 --- /dev/null +++ b/macros/latex/contrib/l3build/l3build-variables.lua @@ -0,0 +1,205 @@ +--[[ + +File l3build-variables.lua Copyright (C) 2018,2019 The LaTeX3 Project + +It may be distributed and/or modified under the conditions of the +LaTeX Project Public License (LPPL), either version 1.3c of this +license or (at your option) any later version. The latest version +of this license is in the file + + http://www.latex-project.org/lppl.txt + +This file is part of the "l3build bundle" (The Work in LPPL) +and all files in that bundle must be distributed together. + +----------------------------------------------------------------------- + +The development version of the bundle can be found at + + https://github.com/latex3/l3build + +for those people who are interested. + +--]] + +local exit = os.exit + +-- "module" is a deprecated function in Lua 5.2: as we want the name +-- for other purposes, and it should eventually be 'free', simply +-- remove the built-in +if type(module) == "function" then + module = nil +end + +-- Ensure the module and bundle exist +module = module or "" +bundle = bundle or "" + +-- Directory structure for the build system +-- Use Unix-style path separators +currentdir = "." +maindir = maindir or currentdir + +-- Substructure for file locations +docfiledir = docfiledir or currentdir +sourcefiledir = sourcefiledir or currentdir +supportdir = supportdir or maindir .. "/support" +testfiledir = testfiledir or currentdir .. "/testfiles" +testsuppdir = testsuppdir or testfiledir .. "/support" + +-- Structure within a development area +builddir = builddir or maindir .. "/build" +distribdir = distribdir or builddir .. "/distrib" +localdir = localdir or builddir .. "/local" +testdir = testdir or builddir .. "/test" +typesetdir = typesetdir or builddir .. "/doc" +unpackdir = unpackdir or builddir .. "/unpacked" + +-- Substructure for CTAN release material +ctandir = ctandir or distribdir .. "/ctan" +tdsdir = tdsdir or distribdir .. "/tds" +tdsroot = tdsroot or "latex" + +-- Location for installation on CTAN or in TEXMFHOME +if bundle == "" then + moduledir = tdsroot .. "/" .. module + ctanpkg = ctanpkg or module +else + moduledir = tdsroot .. "/" .. bundle .. "/" .. module + ctanpkg = ctanpkg or bundle +end + +-- File types for various operations +-- Use Unix-style globs +-- All of these may be set earlier, so a initialised conditionally +auxfiles = auxfiles or {"*.aux", "*.lof", "*.lot", "*.toc"} +bibfiles = bibfiles or {"*.bib"} +binaryfiles = binaryfiles or {"*.pdf", "*.zip"} +bstfiles = bstfiles or {"*.bst"} +checkfiles = checkfiles or { } +checksuppfiles = checksuppfiles or { } +cleanfiles = cleanfiles or {"*.log", "*.pdf", "*.zip"} +demofiles = demofiles or { } +docfiles = docfiles or { } +dynamicfiles = dynamicfiles or { } +excludefiles = excludefiles or {"*~"} +installfiles = installfiles or {"*.sty","*.cls"} +makeindexfiles = makeindexfiles or {"*.ist"} +scriptfiles = scriptfiles or { } +scriptmanfiles = scriptmanfiles or { } +sourcefiles = sourcefiles or {"*.dtx", "*.ins", "*-????-??-??.sty"} +tagfiles = tagfiles or {"*.dtx"} +textfiles = textfiles or {"*.md", "*.txt"} +typesetdemofiles = typesetdemofiles or { } +typesetfiles = typesetfiles or {"*.dtx"} +typesetsuppfiles = typesetsuppfiles or { } +typesetsourcefiles = typesetsourcefiles or { } +unpackfiles = unpackfiles or {"*.ins"} +unpacksuppfiles = unpacksuppfiles or { } + +-- Roots which should be unpacked to support unpacking/testing/typesetting +checkdeps = checkdeps or { } +typesetdeps = typesetdeps or { } +unpackdeps = unpackdeps or { } + +-- Executable names plus following options +typesetexe = typesetexe or "pdflatex" +unpackexe = unpackexe or "tex" +zipexe = zipexe or "zip" + +checkopts = checkopts or "-interaction=nonstopmode" +typesetopts = typesetopts or "-interaction=nonstopmode" +unpackopts = unpackopts or "" +zipopts = zipopts or "-v -r -X" + +-- Engines for testing +checkengines = checkengines or {"pdftex", "xetex", "luatex"} +checkformat = checkformat or "latex" +specialformats = specialformats or { } +specialformats.context = specialformats.context or { + luatex = {binary = "context", format = ""}, + pdftex = {binary = "texexec", format = ""}, + xetex = {binary = "texexec", format = "", options = "--xetex"} + } +specialformats.latex = specialformats.latex or { + etex = {format = "latex"}, + ptex = {binary = "eptex"}, + uptex = {binary = "euptex"} + } +stdengine = stdengine or "pdftex" + +-- The tests themselves +includetests = includetests or {"*"} +excludetests = excludetests or { } + +-- Configs for testing +checkconfigs = checkconfigs or {"build"} + +-- Enable access to trees outside of the repo +-- As these may be set false, a more elaborate test than normal is needed +if checksearch == nil then + checksearch = true +end +if typesetsearch == nil then + typesetsearch = true +end +if unpacksearch == nil then + unpacksearch = true +end + +-- Additional settings to fine-tune typesetting +glossarystyle = glossarystyle or "gglo.ist" +indexstyle = indexstyle or "gind.ist" + +-- Supporting binaries and options +biberexe = biberexe or "biber" +biberopts = biberopts or "" +bibtexexe = bibtexexe or "bibtex8" +bibtexopts = bibtexopts or "-W" +makeindexexe = makeindexexe or "makeindex" +makeindexopts = makeindexopts or "" + +-- Forcing epoch +if forcecheckepoch == nil then + forcecheckepoch = true +end +if forcedocepoch == nil then + forcedocepoch = false +end + +-- Other required settings +asciiengines = asciiengines or {"pdftex"} +checkruns = checkruns or 1 +ctanreadme = ctanreadme or "README.md" +ctanzip = ctanzip or ctanpkg .. "-ctan" +epoch = epoch or 1463734800 +if flatten == nil then + flatten = true +end +maxprintline = maxprintline or 79 +packtdszip = packtdszip or false +typesetcmds = typesetcmds or "" +typesetruns = typesetruns or 2 +recordstatus = recordstatus or false + +-- Extensions for various file types: used to abstract out stuff a bit +bakext = bakext or ".bak" +dviext = dviext or ".dvi" +logext = logext or ".log" +lveext = lveext or ".lve" +lvtext = lvtext or ".lvt" +pdfext = pdfext or ".pdf" +psext = psext or ".ps" +pvtext = pvtext or ".pvt" +tlgext = tlgext or ".tlg" +tpfext = tpfext or ".tpf" + +-- Manifest options +manifestfile = manifestfile or "MANIFEST.md" + +-- Non-standard installation locations +tdslocations = tdslocations or { } + +-- Upload settings +curlexe = curlexe or "curl" +uploadconfig = uploadconfig or {} diff --git a/macros/latex/contrib/l3build/l3build.1 b/macros/latex/contrib/l3build/l3build.1 new file mode 100644 index 0000000000..4addb91317 --- /dev/null +++ b/macros/latex/contrib/l3build/l3build.1 @@ -0,0 +1,67 @@ +.TH l3build 1 "2019-08-24" +.SH NAME +l3build \- Checking and building packages +.SH SYNOPSIS +l3build [] [] +.SH DESCRIPTION +The l3build system is a Lua script for building TeX packages, with particular +emphasis on regression testing. It is written in cross-platform Lua code, so +can be used by any modern TeX distribution with the texlua interpreter. A +package for building with l3build can be written in any TeX dialect; its +defaults are set up for LaTeX packages written in the DocStrip style. +.PP +The most commonly used l3build commands are: +.IP check +Run all automated tests +.IP clean +Clean out directory tree +.IP doc +Typesets all documentation files +.IP install +Installs files into the local texmf tree +.IP save +Saves test validation log +.IP tag +Update release tags in files +.IP uninstall +Uninstalls files from the local texmf tree +.IP unpack +Unpacks the source files into the build tree +.SH OPTIONS +Various options apply +.IP --config|-c +Sets the config(s) used for running tests +.IP --date +Sets the date to insert into sources +.IP --dry-run +Dry run for install +.IP --engine|-e +Sets the engine(s) to use for running test +.IP --epoch +Sets the epoch for tests and typesetting +.IP --first +Name of first test to run +.IP --force|-f +Force tests to run if engine is not set up +.IP --halt-on-error|-H +Stops running tests after the first failure +.IP --last +Name of last test to run +.IP --pdf|-p +Check/save PDF files +.IP --quiet|-q +Suppresses TeX output when unpacking +.IP --rerun +Skip setup: simply rerun tests +.IP --shuffle +Shuffle order of tests +.IP --texmfhome +Location of user texmf tree +.SH BUGS +.SH AUTHOR +The LaTeX3 Project (latex-team@latex-project.org) +.PP +Please log issues on the GitHub homepage: +https://github.com/latex3/l3build/issues. +.SH SEE ALSO +See l3build.pdf for more details. diff --git a/macros/latex/contrib/l3build/l3build.dtx b/macros/latex/contrib/l3build/l3build.dtx new file mode 100644 index 0000000000..e4dbf51fb6 --- /dev/null +++ b/macros/latex/contrib/l3build/l3build.dtx @@ -0,0 +1,2369 @@ +% \iffalse +% +% File l3build.dtx (C) Copyright 2014-2019 The LaTeX3 Project +% +% It may be distributed and/or modified under the conditions of the +% LaTeX Project Public License (LPPL), either version 1.3c of this +% license or (at your option) any later version. The latest version +% of this license is in the file +% +% http://www.latex-project.org/lppl.txt +% +% This file is part of the "l3build bundle" (The Work in LPPL) +% and all files in that bundle must be distributed together. +% +% ----------------------------------------------------------------------- +% +% The development version of the bundle can be found at +% +% https://github.com/latex3/l3build +% +% for those people who are interested. +% +%<*driver> +\RequirePackage{expl3} +\documentclass[full]{l3doc} +\renewcommand\partname{Part} +\usepackage{multicol,needspace} +\makeatletter +\addtolength\columnsep{1em} +\renewcommand\columnseprule{0.4pt} +\def\@starttoc#1{% + \begin{multicols}{2} + \small + \makeatletter + \@input{\jobname.#1}% + \expandafter\newwrite\csname tf@#1\endcsname + \immediate\openout \csname tf@#1\endcsname \jobname.#1\relax + \@nobreakfalse + \end{multicols}} +\makeatother +\newenvironment{buildcmd}[1]{% + \bigskip\par\noindent\hspace{-\parindent}% + \fbox{\ttfamily \textcolor[gray]{0.5}{\$} l3build #1}% + \nopagebreak\smallskip\nopagebreak\par\nopagebreak\noindent\ignorespaces + } + {} +\makeatletter +\newcommand\luavar[1]{\@ifundefined{lua_#1}{\ERROR}{\texttt{\@nameuse{lua_#1}}}} +\newcommand\luavarset[3]{\@namedef{lua_#1}{#2}} +\newcommand\luavarseparator{} +\makeatother +\def\allluavars{ +\luavarset{module}{""}{The name of the module} +\luavarset{bundle}{""}{The name of the bundle in which the module belongs (where relevant)} +\luavarset{ctanpkg}{module/bundle}{Name of the CTAN package matching this module} +\luavarseparator +\luavarset{modules}{\{\}}{The list of all modules in a bundle (when not auto-detecting)} +\luavarset{exclmodules}{\{\}}{Directories to be excluded from automatic module detection} +\luavarseparator +\luavarset{maindir} {"."} {Top level directory for the module/bundle} +\luavarset{docfiledir} {"."} {Directory containing documentation files} +\luavarset{sourcefiledir}{"."} {Directory containing source files} +\luavarset{supportdir} {maindir .. "/support"} {Directory containing general support files} +\luavarset{testfiledir}{"./testfiles"} {Directory containing test files} +\luavarset{testsuppdir}{testfiledir .. "/support"}{Directory containing test-specific support files} +\luavarseparator +\luavarset{builddir} {maindir .. "/build"} {Directory for building and testing} +\luavarset{distribdir}{builddir .. "/distrib"}{Directory for generating distribution structure} +\luavarset{localdir} {builddir .. "/local"} {Directory for extracted files in \enquote{sandboxed} \TeX{} runs} +\luavarset{testdir} {builddir .. "/test"} {Directory for running tests} +\luavarset{typesetdir}{builddir .. "/doc"} {Directory for building documentation} +\luavarset{unpackdir} {builddir .. "/unpack"} {Directory for unpacking sources} +\luavarseparator +\luavarset{ctandir}{distribdir .. "/ctan"}{Directory for organising files for CTAN} +\luavarset{tdsdir} {distribdir .. "/tds"} {Directory for organised files into TDS structure} +\luavarset{tdsroot}{"latex"}{Root directory of the TDS structure for the bundle/module to be installed into} +\luavarseparator +\luavarset{auxfiles} {\{"*.aux", "*.lof", "*.lot", "*.toc"\}}{Secondary files to be saved as part of running tests} +\luavarset{bibfiles} {\{"*.bib"\}}{\BibTeX{} database files} +\luavarset{binaryfiles} {\{"*.pdf", "*.zip"\}}{Files to be added in binary mode to zip files} +\luavarset{bstfiles} {\{"*.bst"\}}{\BibTeX{} style files} +\luavarset{checkfiles} {\{~\}}{Extra files unpacked purely for tests} +\luavarset{checksuppfiles} { }{Files needed for performing regression tests} +\luavarset{cleanfiles} {\{"*.log", "*.pdf", "*.zip"\}}{Files to delete when cleaning} +\luavarset{demofiles} {\{\}}{Files which show how to use a module} +\luavarset{docfiles} {\{\}}{Files which are part of the documentation but should not be typeset} +\luavarset{dynamicfiles} {\{ \}}{Secondary files to cleared before each test is run} +\luavarset{excludefiles} {\{"*\string~"\}}{Files to ignore entirely (default for Emacs backup files)} +\luavarset{installfiles} {\{"*.sty","*.cls"\}}{Files to install to the \texttt{tex} area of the \texttt{texmf} tree} +\luavarset{makeindexfiles} {\{"*.ist"\}}{MakeIndex files to be included in a TDS-style zip} +\luavarset{scriptfiles} {\{ \}}{Files to install to the \texttt{scripts} area of the \texttt{texmf} tree} +\luavarset{scriptmanfiles} {\{ \}}{Files to install to the \texttt{doc/man} area of the \texttt{texmf} tree} +\luavarset{sourcefiles} {\{"*.dtx", "*.ins", "*-????-??-??.sty"\}}{Files to copy for unpacking} +\luavarset{tagfiles} {\{"*.dtx"\}}{Files for automatic tagging} +\luavarset{textfiles} {\{"*.md", "*.txt"\}}{Plain text files to send to CTAN as-is} +\luavarset{typesetdemofiles} {\{\}}{Files to typeset before the documentation for inclusion in main documentation files} +\luavarset{typesetfiles} {\{"*.dtx"\}}{Files to typeset for documentation} +\luavarset{typesetsuppfiles} {\{\}}{Files needed to support typesetting when \enquote{sandboxed}} +\luavarset{typesetsourcefiles}{\{\}}{Files to copy to unpacking when typesetting} +\luavarset{unpackfiles} {\{"*.ins"\}}{Files to run to perform unpacking} +\luavarset{unpacksuppfiles} {\{\}}{Files needed to support unpacking when \enquote{sandboxed}} +\luavarseparator +\luavarset{includetests}{\{"*"\}}{Test names to include when checking} +\luavarset{excludetests}{\{\}} {Test names to exclude when checking} +\luavarseparator +\luavarset{checkdeps} {\{\}}{List of dependencies for running checks} +\luavarset{typesetdeps}{\{\}}{List of dependencies for typesetting docs} +\luavarset{unpackdeps} {\{\}}{List of dependencies for unpacking} +\luavarseparator +\luavarset{checkengines}{\{"pdftex", "xetex", "luatex"\}}{Engines to check with \texttt{check} by default} +\luavarset{stdengine} {"pdftex"}{Engine to generate \texttt{.tlg} file from} +\luavarset{checkformat} {"latex"} {Format to use for tests} +\luavarset{specialformats}{\meta{table}} {Non-standard engine/format combinations} +\luavarseparator +\luavarset{checkconfigs}{\{\}}{Configurations to use for tests} +\luavarseparator +\luavarset{typesetexe} {"pdflatex"} {Executable for compiling \texttt{doc(s)}} +\luavarset{unpackexe} {"tex"} {Executable for running \texttt{unpack}} +\luavarset{zipexe} {"zip"} {Executable for creating archive with \texttt{ctan}} +\luavarset{biberexe} {"biber"} {Biber executable} +\luavarset{bibtexexe} {"bibtex8"} {\BibTeX{} executable} +\luavarset{makeindexexe} {"makeindex"}{MakeIndex executable} +\luavarset{curlexe} {"curl"} {Curl executable for \texttt{upload}} +\luavarseparator +\luavarset{checkopts} {"-interaction=nonstopmode"}{Options passed to engine when running checks} +\luavarset{typesetopts}{"-interaction=nonstopmode"}{Options passed to engine when typesetting} +\luavarset{unpackopts} {""} {Options passed to engine when unpacking} +\luavarset{zipopts} {"-v -r -X"} {Options passed to zip program} +\luavarset{biberopts} {""} {Biber options} +\luavarset{bibtexopts} {"-W"} {\BibTeX{} options} +\luavarset{makeindexopts}{""} {MakeIndex options} +\luavarseparator +\luavarset{checksearch} {true}{Switch to search the system \texttt{texmf} for during checking} +\luavarset{typesetsearch}{true}{Switch to search the system \texttt{texmf} for during typesetting} +\luavarset{unpacksearch} {true}{Switch to search the system \texttt{texmf} for during unpacking} +\luavarseparator +\luavarset{glossarystyle}{"gglo.ist"}{MakeIndex style file for glossary/changes creation} +\luavarset{indexstyle} {"gind.ist"}{MakeIndex style for index creation} +\luavarseparator +\luavarset{forcecheckepoch}{"true"} {Force epoch when running tests} +\luavarset{forcedocepoch} {"false"} {Force epoch when typesetting} +\luavarseparator +\luavarset{asciiengines}{\{"pdftex"\}}{Engines which should log as pure ASCII} +\luavarset{checkruns} {1} {Number of runs to complete for a test before comparing the log} +\luavarset{ctanreadme}{"README.md"} {Name of the file to send to CTAN as \texttt{README.\meta{ext}}} +\luavarset{ctanzip}{ctanpkg ... "-ctan"}{Name of the zip file (without extension) created for upload to CTAN} +\luavarset{epoch} {1463734800} {Epoch (Unix date) to set for test runs} +\luavarset{flatten} {true} {Switch to flatten any source structure when sending to CTAN} +\luavarset{maxprintline}{79} {Length of line to use in log files} +\luavarset{packtdszip} {false} {Switch to build a TDS-style zip file for CTAN} +\luavarset{typesetcmds} {""} {Instructions to be passed to \TeX{} when doing typesetting} +\luavarset{typesetruns}{3} {Number of cycles of typesetting to carry out} +\luavarset{recordstatus}{false} {Switch to include error level from test runs in \texttt{.tlg} files} +\luavarset{manifestfile} {"MANIFEST.md"} {Filename to use for the manifest file} +\luavarseparator +\luavarset{tdslocations}{\{ \}}{Map for non-standard file installations} +\luavarseparator +\luavarset{uploadconfig} {\meta{table}} {Metadata to describe the package for CTAN (see Table~\ref{tab:upload-setup})} +\luavarset{uploadconfig.pkg}{ctanpkg}{Name of the CTAN package} +\luavarseparator +\luavarset{bakext}{".bak"}{Extension of backup files} +\luavarset{dviext}{".dvi"}{Extension of DVI files} +\luavarset{lvtext}{".lvt"}{Extension of log-based test files} +\luavarset{tlgext}{".tlg"}{Extension of test file output} +\luavarset{tpfext}{".tpf"}{Extension of PDF-based test output} +\luavarset{lveext}{".lve"}{Extension of auto-generating test file output} +\luavarset{logext}{".log"}{Extension of checking output, before processing it into a \texttt{.tlg}} +\luavarset{pvtext}{".pvt"}{Extension of PDF-based test files} +\luavarset{pdfext}{".pdf"}{Extension of PDF file for checking and saving} +\luavarset{psext} {".ps"} {Extension of PostScript files} +} +\allluavars + + +\newcommand\luavartypeset{% + \begingroup + \frenchspacing + \renewcommand\luavarset[3]{ + \texttt{##1} & \texttt{##2} & ##3 \\ + } + \renewcommand\luavarseparator{\midrule} + \setlength\LTleft{-0.21\linewidth} + \begin{longtable}{@{}% + >{\small} + p{0.18\linewidth} + @{\hspace{0.03\linewidth}} + >{\footnotesize\hangindent=1em} + p{0.34\linewidth} + @{\hspace{0.03\linewidth}} + >{\small\raggedright\arraybackslash} + p{0.63\linewidth} + @{}} + \toprule + Variable & Default & Description \\ + \midrule\endhead + \allluavars + \bottomrule + \end{longtable} + \endgroup +} +\newcommand\code{\texttt} +\newcommand\var{\texttt} +\usepackage[procnames]{listings} +\lstset{ + basicstyle=\ttfamily\small, + numbers=left, + numberstyle={\tiny\color[gray]{0.4}}, +} +\usepackage{shortvrb} +\usepackage{enumitem} +\usepackage{longtable} +\MakeShortVerb\| +\begin{document} + \DocInput{\jobname.dtx} +\end{document} +% +% \fi +% +% \title{^^A +% The \pkg{l3build} package\\ Checking and building packages^^A +% } +% +% \author{^^A +% The \LaTeX3 Project\thanks +% {^^A +% E-mail: +% \href{mailto:latex-team@latex-project.org} +% {latex-team@latex-project.org}^^A +% }^^A +% } +% +% \date{Released 2019-08-24} +% +% \maketitle +% \tableofcontents +% +% \begin{documentation} +% +% \section{The \pkg{l3build} system} +% +% \subsection{Introduction} +% +% The \pkg{l3build} system is a Lua script for building \TeX{} packages, with particular emphasis on regression testing. +% It is written in cross-platform Lua code, so can be used by any modern \TeX{} distribution with the |texlua| interpreter. +% Wrapper functions/binaries are distributed in the standard \TeX{} distributions so that the script can be called using +% \texttt{l3build} on the command line; run without arguments it prints a brief synopsis of its usage. +% +% The \pkg{l3build} system is designed for packages written in any \TeX\ dialect; its defaults are set up for \LaTeX\ packages written in the DocStrip style. (Caveat: minimal testing has yet been performed for non-\LaTeX{} packages.) +% +% Test files are written as standalone \TeX{} documents using the |regression-test.tex| setup file; documentation on writing these tests is discussed in Section~\ref{sec:writing-tests}. +% +% Each package will define its own |build.lua| configuration file which both sets variables (such as the name of the package) and may also provide custom functions. +% +% \pagebreak[2] +% A standard package layout might look something like the following: +% \begin{Verbatim}[fontsize=\small] +% abc/ +% abc.dtx +% abc.ins +% build.lua +% README.md +% support/ +% testfiles/ +% \end{Verbatim} +% Most of this should look fairly self-explanatory. +% The top level |support/| directory (optional) would contain any necessary files for compiling documentation, running regression tests, and so on. +% +% The \pkg{l3build} system is also capable of building and checking \emph{bundles} of packages. +% To avoid confusion, we refer to either a standalone package or a package within a bundle as a \emph{module}. +% +% For example, within the \LaTeX3 project we have the \textsf{l3packages} bundle which contains the \textsf{xparse}, \textsf{xtemplate}, etc., modules. +% These are all built and distributed as one bundle for installation, distribution \emph{via} CTAN and so forth. +% +% Each module in a bundle will have its own build script, and a bundle build script brings them all together. +% A standard bundle layout would contain the following structure. +% \begin{Verbatim} +% mybundle/ +% build.lua +% support/ +% yyy/ zoo/ +% build.lua build.lua +% README.md README.md +% testfiles/ testfiles/ +% yyy.dtx zoo.dtx +% yyy.ins zoo.ins +% \end{Verbatim} +% All modules within a bundle must use the same build script name. +% +% In a small number of cases, the name used by CTAN for a module or bundle is +% different from that used in the installation tree. For example, the \LaTeXe{} +% kernel is called \pkg{latex-base} by CTAN but is located inside +% \texttt{\meta{texmf}/tex/latex/base}. This can be handled by using +% \var{ctanpkg} for the name required by CTAN to override the standard +% value. +% +% The |testfiles/| folder is local to each module, and its layout consists of a series of regression tests with their outputs. +% \begin{Verbatim} +% testfiles/ +% test1.lvt +% test1.tlg +% ... +% support/ +% my-test.cls +% \end{Verbatim} +% Again, the |support/| directory contains any files necessary to run some or all of these tests. +% +% When the build system runs, it creates a directory |build/| for various unpacking, compilation, and testing purposes. +% For a module, this build folder can be in the main directory of the package itself, but for a bundle it should be common for the bundle itself and for all modules within that bundle. +% A |build/| folder can be safety deleted; all material within is re-generated for each command of the \pkg{l3build} system. +% +% \subsection{Main build commands} +% +% In the working directory of a bundle or module, \pkg{l3build} is run by executing +% \begin{center} +% \texttt{l3build \meta{command} [\meta{option(s)}]} +% \end{center} +% where \texttt{\meta{command}} can be one of the following: +% \begin{itemize}[noitemsep]\ttfamily +% \item check +% \item check \meta{name(s)} +% \item clean +% \item ctan +% \item doc +% \item doc \meta{name(s)} +% \item install +% \item manifest +% \item save \meta{name(s)} +% \item tag \meta{tag} +% \item uninstall +% \item unpack +% \item upload +% \item upload \meta{tag} +% \end{itemize} +% These commands are described below. +% +% As well as these commands, the system recognises the options +% \begin{itemize} +% \item |--config| (|-c|) Configuration(s) to use for testing +% \item |--date| Date to use when tagging data +% \item |--debug| Runs the target in debug mode (not supported by all targets) +% \item |--dirty| Skip cleaning up of the test area +% \item |--dry-run| Runs the \texttt{install} target but does not copy +% any files: simply lists those that would be installed +% \item |--email| Sets the email address for CTAN upload +% \item |--engine| (|-e|) Sets the engine to use for testing +% \item |--epoch| Sets the epoch for typesetting and testing +% \item |--file| (|-F|) Take the upload announcement from the given file +% \item |--first| Name of the first test to run +% \item |--force| (|-f|) Force checks to run even if sanity +% checks fail, \emph{e.g.}~when |--engine| is not given in +% \luavar{checkengines} +% \item |--full| Instructs the \texttt{install} target to include the +% \texttt{doc} and \texttt{source} trees +% \item |--halt-on-error| (|-H|) Specifies that checks +% should stop as soon as possible, rather than running all requested +% tests; the difference file is printed in the terminal directly in the case of failure +% \item |--last| Name of the last test to run +% \item |--message| (|-m|) Text for upload announcement +% \item |--quiet| (|-q|) Suppresses output from unpacking +% \item |--rerun| Run tests without unpacking/set up +% \item |--show-log-on-error| To be used in addition to \texttt{--halt-on-error} and results +% in the full \texttt{.log} file of a failed test to be shown on the console +% \item |--shuffle| Shuffle the order in which tests run +% \item |--texmfhome| Sets the location of the user tree for installing +% \end{itemize} +% +% \begin{buildcmd}{check} +% The |check| command runs the entire test suite. +% This involves iterating through each \texttt{.lvt} file in the test directory (specified by the \var{testfiledir} variable), compiling each test in a \enquote{sandbox} (a directory specified by \var{testdir}), and comparing the output against each matching predefined \texttt{.tlg} file. +% +% If changes to the package or the typesetting environment have affected the results, the check for that file fails. +% A |diff| of the expected to actual output should then be inspected to determine the cause of the error; it is located in the \var{testdir} directory (default \luavar{testdir}). +% +% On Windows, the |diff| program is not available and so |fc| is used instead +% (generating an |.fc| file). Setting the environmental variables |diffexe| +% and |diffext| can be used to adjust the choice of comparison made: the standard +% values are +% \begin{itemize} +% \item[Windows] |diffext = fc|, |diffexe = fc /n| +% \item[*nix] |diffext = diff|, |diffexe = diff -c --strip-trailing-cr| +% \end{itemize} +% +% The following files are moved into the \enquote{sandbox} for the |check| process: +% \begin{itemize} +% \item all \var{installfiles} after unpacking; +% \item all \var{checkfiles} after unpacking; +% \item any files in the directory \var{testsuppdir}; +% \item any files that match \var{checksuppfiles} in the \var{supportdir}. +% \end{itemize} +% This range of possibilities allow sensible defaults but significant flexibility for defining your own test setups. +% +% Checking can be performed with any or all of the `engines' \texttt{pdftex}, \texttt{xetex}, and \texttt{luatex}. +% By default, each test is executed with all three, being compared against the \texttt{.tlg} file produced from the \var{pdftex} engine (these defaults are controlled by the |checkengines| and |stdengine| variable respectively). +% The format used for tests can be altered by setting \var{checkformat}: the default setting \texttt{latex} means that tests are run using \emph{e.g.}~\texttt{pdflatex}, whereas setting to \texttt{plain} will run tests using \emph{e.g.}~\texttt{pdftex}. +% (Currently, this should be one of \texttt{latex} or \texttt{plain}.) +% To perform the check, the engine typesets each test up to \var{checkruns} times. +% More detail on this in the documentation on |save|. +% Options passed to the binary are defined in the variable \var{checkopts}. +% +% By default, |texmf| trees are searched for input files when checking. This can be disabled by setting \var{checksearch} to |false|: isolation provides confidence that the tests cannot accidentally be running with incorrect files installed in the main distribution or |hometexmf|. +% \end{buildcmd} +% +% +% \begin{buildcmd}{check \meta{name(s)}} +% Checks only the test \texttt{\meta{name(s)}.lvt}. +% All engines specified by \var{checkengines} are tested unless the command +% line option |--engine| (or |-e|) has been given to limit +% testing to a single engine. Normally testing is preceded by unpacking +% source files and copying the result plus any additional support to the +% test directory: this may be skipped using the |-s| option. +% \end{buildcmd} +% +% \begin{buildcmd}{clean} +% This command removes all temporary files used for package bundling and regression testing. +% In the standard layout, these are all files within the directories defined by \var{localdir}, \var{testdir}, \var{typesetdir} and \var{unpackdir}, as well as all files defined in the \var{cleanfiles} variable in the same directory as the script. +% The defaults are |.pdf| files from typesetting (|doc|) and |.zip| files from bundling (|ctan|). +% \end{buildcmd} +% +% +% \begin{buildcmd}{ctan} +% Creates an archive of the package and its documentation, suitable for uploading to CTAN. +% The archive is compiled in \var{distribdir}, and if the results are successful the resultant |.zip| file is moved into the same directory as the build script. +% If \var{packtdszip} is set true then the building process includes a |.tds.zip| file containing the `\TeX\ Directory Structure' layout of the package or bundle. +% The archive therefore may contain two `views' of the package: +% \begin{Verbatim} +% abc.zip/ +% abc/ +% abc.dtx +% abc.ins +% abc.pdf +% README.md +% abc.tds.zip/ +% doc/latex/abc/ +% abc.pdf +% README.md +% source/latex/abc/ +% abc.dtx +% abc.ins +% tex/latex/abc/ +% abc.sty +% \end{Verbatim} +% The files copied into the archive are controlled by a number of variables. +% The `root' of the TDS structure is defined by \var{tdsroot}, which is \luavar{tdsroot} by default. Plain users would redefine this to |"plain"| (or perhaps |"generic"|), for example. +% The build process for a |.tds.zip| file currently assumes a `standard' +% structure in which all extracted files should be placed inside the |tex| +% tree in a single directory, as shown above. If the module includes any +% \BibTeX{} or MakeIndex styles these will be placed in the appropriate +% subtrees. +% +% \pagebreak[2] +% The |doc| tree is constructed from: +% \begin{itemize} +% \item all files matched by \var{demofiles}, +% \item all files matched by \var{docfiles}, +% \item all files matched by \var{typesetfiles} with their extension replaced with |.pdf|, +% \item all files matched by \var{textfiles}, +% \item all files matched by \var{bibfiles}. +% \end{itemize} +% The |source| tree is constructed from all files matched by \var{typesetfiles} and \var{sourcefiles}. +% The |tex| tree from all files matched by \var{installfiles}. +% +% The special case \var{ctanreadme} is used to allow renaming of a local +% |foo.xyz| file to |README.xyz|. The local |foo.xyz| should be listed in +% \var{textfiles}, and will be renamed as part of constructing the CTAN +% structure. The file extension will be unchanged by this process. +% +% Files that should always be excluded from the archive are matched against the \var{excludefiles} variable; by default this is \luavar{excludefiles}, which match Emacs' autosave files. +% +% Binary files should be specified with the \var{binaryfiles} variable (default \luavar{binaryfiles}); these are added to the zip archive without normalising line endings (text files are automatically converted to Unix-style line endings). +% +% To create the archive, by default the binary \var{zipexe} is used (\luavar{zipexe}) with options \var{zipopts} (|-v -r -X|). The intermediate build directories \var{ctandir} and \var{tdsdir} are used to construct the archive. +% \end{buildcmd} +% +% +% \begin{buildcmd}{doc} +% Compiles documentation files in the \var{typesetdir} directory. +% In the absence of one or more file names, all documentation is typeset; +% a file list may be given at the command line for selective typesetting. +% If the compilation is successful the |.pdf| is moved back into the main directory. +% +% The documentation compilation is performed with the \var{typesetexe} binary (default \texttt{pdflatex}), with options \var{typesetopts}. +% Additional \TeX{} material defined in \var{typesetcmds} is passed to the document (e.g., for writing |\\PassOptionsToClass{l3doc}{letterpaper}|, and so on---note that backslashes need to be escaped in Lua strings). +% +% Files that match |typesetsuppfiles| in the |support| directory (|supportdir|) are copied into the |build/local| directory (|localdir|) for the typesetting compilation process. +% Additional dependencies listed in the \var{typesetdeps} variable (empty by default) will also be installed. +% +% Source files specified in \var{sourcefiles} and \var{typesetsourcefiles} +% are unpacked before the typesetting takes place. (In most cases +% \var{typesetsourcefiles} will be empty, but may be used where there are +% files to unpack \emph{only} for typesetting.) +% +% If \var{typesetsearch} is \code{true} (default), standard \texttt{texmf} search trees are used in the typesetting compilation. If set to false, \emph{all} necessary files for compilation must be included in the |build/local| sandbox. +% +% \end{buildcmd} +% +% \begin{buildcmd}{doc \meta{name(s)}} +% Typesets only the files with the \meta{name(s)} given, which should be the +% basename without any extension. +% \end{buildcmd} +% +% \begin{buildcmd}{install} +% Copies all package files (defined by \var{installfiles}) into the user's home \texttt{texmf} tree in the form of the \TeX\ Directory Structure. +% The location of the user tree can be adjusted using the |--texmfhome| swtich: +% the standard setting is the location set as |TEXMFHOME|. +% \end{buildcmd} +% +% \begin{buildcmd}{save \meta{name(s)}} +% This command runs through the same execution as |check| for a specific test(s) \texttt{\meta{name(s)}.lvt}. +% This command saves the output of the test to a |.tlg| file. +% This file is then used in all subsequent checks against the \texttt{\meta{name}.lvt} test. +% +% If the |--engine| (or |-e|) is specified (one of |pdftex|, |xetex|, or |luatex|), the saved output is stored in \texttt{\meta{name}.\meta{engine}.tlg}. This is necessary if running the test through a different engine produces a different output. +% A normalisation process is performed when checking to avoid common differences such as register allocation; full details are listed in section~\ref{sec:norm}. +% +% If the \var{recordstatus} variable is set \var{true}, additional information +% will be added to the \texttt{.tlg} to record the \enquote{exit status} of the +% typesetting compilation of the \texttt{.lvt} file. If the typesetting compilation +% completed without throwing an error (due to \TeX\ programming errors, for example), +% the \enquote{exit status} is zero, else non-zero. +% \end{buildcmd} +% +% \begin{buildcmd}{manifest} +% Generates a `manifest' file which lists the files of the package as known to \pkg{l3build}. +% The filename of this file (by default \luavar{manifestfile}) can be set with the variable \var{manifestfile}. +% +% The intended purpose of this manifest file is to include it within a package as metadata. +% This would allow, say, for the copyright statement for the package to refer to the +% manifest file rather than requiring the author to manually keep a file list up-to-date +% in multiple locations. The manifest file can be structured and documented with a degree +% of flexibility. Additional information is described in Section~\ref{sec:manifest}. +% +% In order for \texttt{manifest} to detect derived and typeset files, it should be run +% \emph{after} running \texttt{unpack} and \texttt{doc}. If \texttt{manifest} +% is run after also running \texttt{ctan} it will include the files included +% in the CTAN and TDS directories as well. +% +% Presently, this means that if you wish to include an up-to-date manifest file +% as part of a \texttt{ctan} release, you must run +% \texttt{ctan} / \texttt{manifest} / \texttt{ctan}. +% Improvements to this process are planned for the future. +% \end{buildcmd} +% +% \begin{buildcmd}{tag} +% Modifies the contents of files specified by |tagfiles| using a search pattern +% to automatically update the `release tag' (or package version) and date. +% The tag is given as the command line option and the date using +% |--date| (|-d|). If not given, the date will default to the current date in +% ISO format (YYYY-MM-DD). If no option is given (i.e., no tag specified) the tag +% is passed through as |nil| to allow setting the tag programmatically. +% +% The standard setup has no search pattern defined for this target and so no action +% will be taken \emph{unless} tag substitution is set up by defining the Lua function +% |update_tag()|. See Section~\ref{sec:tagging} for full details on this feature. +% \end{buildcmd} +% +% \begin{buildcmd}{unpack} +% This is an internal target that is normally not needed on user level. +% It unpacks all files into the directory defined by \var{unpackdir}. This occurs before other build commands such as |doc|, |check|, etc. +% +% The unpacking process is performed by executing the \var{unpackexe} (default \texttt{tex}) with options \var{unpackopts} on all files defined by the \var{unpackfiles} variable; by default, all files that match \luavar{unpackfiles}. +% +% If additional support files are required for the unpacking process, these can be enumerated in the \var{unpacksuppfiles} variable. +% Dependencies for unpacking are defined with \var{unpackdeps}. +% +% By default this process allows files to be accessed in all standard |texmf| trees; this can be disabled by setting \var{unpacksearch} to |false|. +% \end{buildcmd} +% +% \begin{buildcmd}{upload} +% This target uses \texttt{curl} to upload the package zip file (created using \texttt{ctan}) to CTAN. +% To control the metadata used to upload the package, the \texttt{uploadconfig} table should be populated with a number of fields. +% These are documented in Table~\ref{tab:upload-setup}. +% Missing required fields will result in an interactive prompt for manual entry. +% +% See Section~\ref{sec:upload} for full details on this feature. +% \end{buildcmd} +% +% \subsection{The \texttt{build.lua} file} +% +% The \texttt{build.lua} file used to control \pkg{l3build} is a simple Lua file +% which is read during execution. In the current release of \pkg{l3build}, +% \texttt{build.lua} is read automatically and can access all of the global +% functions provided by the script. Thus it may contain a simple list of +% variable settings \emph{or} additionally custom code to change the build +% process. A number of example scripts are given in Section~\ref{sec:examples}. +% +% \subsection{Example build scripts} +% \label{sec:examples} +% +% An example of a standalone build script for a package that uses self-contained |.dtx| files is shown in Figure~\ref{fig:breqn}. +% Here, the |module| only is defined, and since it doesn't use |.ins| files so the variable \var{unpackfiles} is redefined to run |tex| on the |.dtx| files instead to generate the necessary |.sty| files. +% There are some PDFs in the repository that shouldn't be part of a CTAN submission, so they're explicitly excluded, and here unpacking is done `quietly' to minimise console output when building the package. +% +% \begin{figure}[!b] +% \begin{lstlisting}[frame=single,language={[5.2]Lua},gobble = 6] +% -- Build configuration for breqn +% +% module = "breqn" +% +% unpackfiles = {"*.dtx"} +% excludefiles = {"*/breqn-abbr-test.pdf", +% "*/eqbreaks.pdf"} +% unpackopts = "-interaction=batchmode" +% \end{lstlisting} +% \caption{The build configuration for the \pkg{breqn} package.} +% \label{fig:breqn} +% \end{figure} +% +% An example of a bundle build script for \pkg{l3packages} is shown in Figure~\ref{fig:bundle}. +% Note for \LaTeX3 we use a common file to set all build variables in one place, and the path to the |l3build.lua| script is hard-coded so we always use our own most recent version of the script. +% An example of an accompanying module build script is shown in Figure~\ref{fig:module}. +% +% \begin{figure}[p] +% \begin{lstlisting}[frame=single,language={[5.2]Lua},gobble = 6] +% -- Build script for LaTeX3 "l3packages" files +% +% -- Identify the bundle: there is no module as this is the "driver" +% bundle = "l3packages" +% +% -- Location of main directory: use Unix-style path separators +% maindir = ".." +% \end{lstlisting} +% \caption{The build script for the \pkg{l3packages} bundle.} +% \label{fig:bundle} +% \end{figure} +% +% \begin{figure}[p] +% \begin{lstlisting}[frame=single,language={[5.2]Lua},gobble = 6] +% -- Build script for LaTeX3 "xparse" files +% +% -- Identify the bundle and module: +% bundle = "l3packages" +% module = "xparse" +% +% -- Location of main directory: use Unix-style path separators +% -- Should match that defined by the bundle. +% maindir = "../.." +% \end{lstlisting} +% \caption{The build script for the \pkg{xparse} module.} +% \label{fig:module} +% \end{figure} +% +% \subsection{Backwards compatibility} +% +% Earlier releases of \pkg{l3build} required that the last line of +% \texttt{build.lua} ran the main script, \emph{i.e.}~ that \texttt{build.lua} +% was what the user called rather than \texttt{l3build.lua}. To allow scripts +% to support both forms \emph{for the transition}, a simple test may be +% included as showing in Figure~\ref{fig:build-compat}. +% \begin{figure} +% \begin{lstlisting}[frame=single,language={[5.2]Lua},gobble = 6] +% if not release_date then +% dofile(kpse.lookup("l3build.lua")) +% end +% \end{lstlisting} +% \caption{Final lines for a \texttt{build.lua} script usable with both older +% and newer releases of \pkg{l3build}.} +% \label{fig:build-compat} +% \end{figure} +% +% Note that in time support for loading \pkg{l3build} by calling the +% \texttt{build.lua} script \emph{may} be removed: the recommended approach for +% new scripts is to run \texttt{l3build}. +% +% \subsection{Variables} +% +% This section lists all variables defined in the |l3build.lua| script that are available for customisation. +% +% \luavartypeset +% +% \subsection{Interaction between tests} +% +% Tests are run in a single directory, so whilst they are may be isolated from +% the system \TeX{} tree they do share files. This may be significant if +% installation-type files are generated during a test, for example by a +% |filecontents| environment in \LaTeX{}. Typically, you should set up your +% tests such that they do not use the same names for such files: this may lead +% to variable outcomes depending on the order in which tests are run. +% +% \subsection{Selective running of tests} +% +% The variables |includetests| and |excludetests| may be used to select which +% tests are run: these variables take test \emph{names} not full file names. +% The list of tests in |excludetests| overrides any matches in |includetests|, +% meaning that tests can be disabled selectively. It also makes it possible +% to disable test on for example a platform basis: the Lua core variable +% |os.type| may be used to set |excludetests| only on some systems. +% +% \subsection{Multiple sets of tests} +% +% In most cases, a single set of tests will be appropriate for the module, with +% a common set of configuration settings applying. However, there are +% situations where you may need entirely independent sets of tests which have +% different setting values, for example using different formats or where the +% entire set will be engine-dependent. To support this, \pkg{l3build} offers +% the possibility of using multiple configurations for tests. This is supported +% using the \var{checkconfigs} table. This is used to list the names of each +% configuration (|.lua| file) which will be used to run tests. +% +% For example, for the core \LaTeXe{} tests the main test files are contained +% in a directory |testfiles|. To test font loading for \XeTeX{} and \LuaTeX{} +% there are a second set of tests in |testfiles-TU| which use the short +% |build-TU.lua| file shown in Figure~\ref{fig:configs}. To run both sets of +% tests, the main |build.lua| file contains the setting +% |checkconfigs = {"build", "config-TU"}|. This will cause \pkg{l3build} to run +% first using no additional settings (\emph{i.e.}~reading the normal +% |build.lua| file alone), then running \emph{also} loading the settings from +% |config-TU.lua|. +% \begin{figure} +% \begin{lstlisting}[frame=single,language={[5.2]Lua},gobble = 6] +% -- Special config for these tests +% checksearch = true +% checkengines = {"xetex","luatex"} +% testfiledir = "testfiles-TU" +% \end{lstlisting} +% \caption{Example of using additional (or overriding) settings for configuring tests in a different subdirectory.} +% \label{fig:configs} +% \end{figure} +% +% To allow selection of a one or more configurations, and to allow saving of +% |.tlg| files in non-standard configurations, the |--config| (|-c|) option may +% be used. This works in the same way as |--engine|: it takes a comma list of +% configurations to apply, overriding \var{checkconfigs}. +% +% \subsection{Dependencies} +% +% If you have multiple packages that are developed separately but still interact in some way, it's often desirable to integrate them when performing regression tests. +% For \LaTeX3, for example, when we make changes to \pkg{l3kernel} it's important to check that the tests for \pkg{l3packages} still run correctly, so it's necessary to include the \pkg{l3kernel} files in the build process for \pkg{l3packages}. +% +% In other words, \pkg{l3packages} is \emph{dependent} on \pkg{l3kernel}, and this is specified in \pkg{l3build} by setting appropriately the variables \texttt{checkdeps}, \texttt{typesetdeps}, and \texttt{unpackdeps}. +% The relevant parts of the \LaTeX3 repository is structured as the following. +% \Needspace{3\baselineskip} +% \begin{Verbatim} +% l3/ +% l3kernel/ +% build.lua +% expl3.dtx +% expl3.ins +% ... +% testfiles/ +% l3packages/ +% build.lua +% xparse/ +% build.lua +% testfiles/ +% xparse.dtx +% xparse.ins +% support/ +% \end{Verbatim} +% For \LaTeX3 build files, |maindir| is defined as top level folder |l3|, so all support files are located here, and the build directories will be created there. +% To set \pkg{l3kernel} as a dependency of \pkg{l3package}, within |l3packages/xparse/build.lua| the equivalent of the following is set: +% \begin{Verbatim} +% maindir = "../.." +% checkdeps = {maindir .. "/l3kernel"} +% \end{Verbatim} +% This ensures that the \pkg{l3kernel} code is included in all processes involved in unpacking and checking and so on. +% The name of the script file in the dependency is set with the |scriptname| variable; by default these are |"build.lua"|. +% +% \subsection{Non-standard source layouts} +% +% A variety of source layouts are supported. In general, a \enquote{flat} +% layout with all source files \enquote{here} is most convenient. However, +% \pkg{l3build} supports placement of both code and documentation source +% files in other locations using the \var{sourcefiledir} and \var{docfiledir} +% variables. For pre-built trees, the glob syntax \texttt{**/*.\meta{ext}} may +% be useful in these cases: this enables recursive searching in the appropriate +% tree locations. With the standard settings, this structure will be removed +% when creating a CTAN release: the variable \var{flatten} may be +% used to control this behavior. +% +% A series of example layouts and matching |build.lua| files are available from +% \url{https://github.com/latex3/l3build/tree/master/examples}. +% +% \subsection{Non-standard formats/binaries} +% +% The standard approach used by \pkg{l3build} is to use a combination +% of \var{engine} and \var{checkformat} to generate the \emph{binary} and +% \emph{format} combination used for tests. For example, when |pdftex| is +% the \var{engine} and |latex| is the \var{checkformat}, the system call +% used is +% \begin{verbatim} +% pdftex --fmt=pdflatex +% \end{verbatim} +% \emph{i.e.}~the binary names is the same as the \var{engine}, and the format +% is a simple substitution of the \var{checkformat} into \var{engine}, replacing +% |tex|. +% +% For more complex set ups, \var{specialformats} should be used. This is a +% table with one entry per \var{checkformat}. Each entry is itself a table, +% and these contain a list of engines and settings for |binary|, |format| +% and |options|. For example, for Con\TeX{}t and appropriate set up is +% \begin{verbatim} +% specialformats.context = { +% luatex = {binary = "context", format = ""}, +% pdftex = {binary = "texexec", format = ""}, +% xetex = {binary = "texexec", format = "", options = "--xetex"} +% } +% \end{verbatim} +% +% \subsection{Output normalisation} +% \label{sec:norm} +% +% To allow test files to be used between different systems (\emph{e.g.}~when +% multiple developers are involved in a project), the log files are normalised +% before comparison during checking. This removes some system-dependent +% data but also some variations due to different engines. This normalisation +% consists of two parts: removing (\enquote{ignoring}) some lines and modifying +% others to give consistent test. Currently, the following types of line are +% ignored: +% \begin{itemize} +% \item Lines before the \cs{START}, after the \cs{END} and within +% \cs{OMIT}/\cs{TIMO} blocks +% \item Entirely blank lines, including those consisting only of spaces. +% \item Lines related to loading |.fd| files (from \texttt{(\meta{name}.fd} +% to the matching \texttt{)}). +% \item Lines starting \cs{openin} or \cs{openout}. +% \end{itemize} +% Modifications made in lines are: +% \begin{itemize} +% \item Removal spaces at the start of lines. +% \item Removal of |./| at start of file names. +% \item Standardisation of the list of units known to \TeX{} (\pdfTeX{} +% and \LuaTeX{} add a small number of additional units which are not +% known to \TeX90 or \XeTeX{}, (u)p\TeX{} adds some additional non-standard +% ones) +% \item Standardisation of \verb*|\csname\endcsname | to |\csname\endcsname| +% (the former is formally correct, but the latter was produced for many +% years due to a \TeX{} bug). +% \item Conversion of \texttt{on line \meta{number}} to \texttt{on line ...} +% to allow flexibility in changes to test files. +% \item Conversion of file dates to \texttt{....-..-..}, and any version +% numbers on the same lines to \texttt{v...}. +% \item Conversion of register numbers in assignment lines +% \texttt{\cs{\meta{register}}=\cs{\meta{type}}\meta{number}} to +% \texttt{\cs{\meta{type}}\meta{...}} +% \item Conversion of box numbers in |\show| lines +% \texttt{>~\cs{box}\meta{number}=} to \texttt{>~\cs{box}...=} +% \item Removal of some (u)p\TeX{} data where it is equivalent to +% \pdfTeX{} (|yoko direction|, |\displace 0.0|) +% \end{itemize} +% +% \LuaTeX{} makes several additional changes to the log file. As normalising +% these may not be desirable in all cases, they are handled separately. +% When creating \LuaTeX{}-specific test files (either with \LuaTeX{} as +% the standard engine or saving a \LuaTeX{}-specific |.tlg| file) no further +% normalisation is undertaken. On the other hand, for cross-engine comparison +% the following normalisation is applied: +% \begin{itemize} +% \item Removal of additional (unused) |\discretionary| points. +% \item Normalisation of some |\discretionary| data to a \TeX{}90 form. +% \item Removal of |U+...| notation for missing characters. +% \item Removal of |display| for display math boxes +% (included by \TeX90/\pdfTeX{}/\XeTeX). +% \item Removal of Omega-like |direction TLT| information. +% \item Removal of additional whatsit containing local paragraph information +% (|\localinterlinepenalty|, \emph{etc.}). +% \item Rounding of glue set to four decimal places (glue set may be +% slightly different in \LuaTeX{} compared to other engines). +% \item Conversion of low chars ($0$ to $31$) to |^^| notation. +% \end{itemize} +% +% When making comparisons between 8-bit and Unicode engines it is useful to +% format the top half of the 8-bit range such that it appears in the log as +% |^^|\texttt{\meta{char}} (the exact nature of the 8-bit output is otherwise +% dependent on the active code page). This may be controlled using the +% |asciiengines| option. Any engines named here will use a |.tcx| file to +% produce only ASCII chars in the log output, whilst for other engines +% normalisation is carried out from UTF-8 to ASCII. If the option is set to +% an empty table the latter process is skipped: suitable for cases where only +% Unicode engines are in use. +% +% \section{Writing test files} +% \label{sec:writing-tests} +% +% Test files are written in a \TeX{} dialect using the support file |regression-test.tex|, which should be |\input| at the very beginning of each test. +% Additional customisations to this driver can be included in a local |regression-test.cfg| file, which will be loaded automatically if found. +% +% The macros loaded by |regression-test.tex| set up the test system and provide a number of commands to aid the production of a structured test suite. +% The basis of the test suite is to output material into the |.log| file, from which a normalised test output (|.tlg|) file is produced by the build command |save|. +% A number of commands are provided for this; they are all written in uppercase to help avoid possible conflicts with other package commands. +% +% \subsection{Metadata and structural commands} +% +% Any commands that write content to the |.log| file that should be ignored can be surrounded by |\OMIT| \dots\ |\TIMO|. +% At the appropriate location in the document where the |.log| comparisons should start (say, after |\begin{document}|), the test suite must contain the |\START| macro. +% The test should then include \cs{AUTHOR}\marg{authors details} in case a test file fails in the future and needs to be re-analysed. +% +% Some additional diagnostic information can then be included as metadata for the conditions of the test. +% The desired format can be indicated with \cs{FORMAT}\marg{format name}, and any packages or classes loaded can be indicated with +% +% \noindent\hspace*{2\parindent} \cs{CLASS}\oarg{options}\marg{class name, version}\par +% \noindent\hspace*{2\parindent} \cs{PACKAGE}\oarg{options}\marg{package name, version} +% +% \noindent These do not provide information that is useful for automated checking; after all, packages change their version numbers frequently. +% Rather, including this information in a test indicates the conditions under which the test was definitely known to pass at a certain time in the past. +% +% The |\END| command signals the end of the test (but read on). +% Some additional diagnostic information is printed at this time to debug if the test did not complete `properly' in terms of mismatched brace groups or \cs{if}\dots\cs{fi} groups. +% +% In a \LaTeX{} document, |\end{document}| will implicitly call |\END| at the very end of the compilation process. +% If |\END| is used directly (replacing |\end{document}| in the test), the compilation will halt almost immediately, and various tasks that |\end{document}| usually performs will not occur (such as potentially writing to the various |.toc| files, and so on). This can be an advantage if there is additional material printed to the log file in this stage that you wish to ignore, but it is a disadvantage if the test relies on various auxiliary data for a subsequent typesetting run. +% (See the \var{checkruns} variable for how these tests would be test up.) +% +% \subsection{Commands to help write tests} +% +% A simple command \cs{CHECKCOMMAND}\cs{\meta{macro}} is provided to check whether a particular \cs{\meta{macro}} is defined, undefined, or equivalent to \cs{relax}. +% This is useful to flag either that internal macros are remaining local to their definitions, or that defined commands definitely are defined, or even as a reminder that commands you intend to define in a future package need to be tested once they appear. +% +% \cs{TYPE} is used to write material to the \texttt{.log} file, like \LaTeX's \cs{typeout}, but it allows `long' input. +% The following commands are defined to use \cs{TYPE} to output strings to the \texttt{.log} file. +% \begin{itemize} +% \item +% \cs{SEPARATOR} inserts a long line of \texttt{=} symbols to break up the log output. +% \item +% \cs{NEWLINE} inserts a linebreak into the log file. +% \item +% \cs{TRUE}, \cs{FALSE}, \cs{YES}, \cs{NO} output those strings to the log file. +% \item +% \cs{ERROR} is \emph{not} defined but is commonly used to indicate a code path that should never be reached. +% \item +% The \cs{TEST}\marg{title}\marg{contents} command surrounds its \meta{contents} with some \cs{SEPARATOR}s and a \meta{title}. +% \item +% \cs{TESTEXP} surrounds its contents with \cs{TYPE} and formatting to match \cs{TEST}; this can be used as a shorthand to test expandable commands. +% \item +% TODO: would a \cs{TESTFEXP} command (based on \cs{romannumeral} expansion) be useful as well? +% \item +% \cs{BEGINTEST}\marg{title} \dots \cs{ENDTEST} is an environment form of +% \cs{TEST}, allowing verbatim material, \emph{etc.} to appear. +% \end{itemize} +% An example of some of these commands is shown following. +% \begin{Verbatim} +% \TEST{bool_set,~lazy~evaluation} +% { +% \bool_set:Nn \l_tmpa_bool +% { +% \int_compare_p:nNn 1=1 +% && \bool_lazy_any_p:n +% { +% { \int_compare_p:nNn 2=3 } +% { \int_compare_p:nNn 4=4 } +% { \int_compare_p:nNn 1=\ERROR } % is skipped +% } +% && \int_compare_p:nNn 2=2 +% } +% \bool_if:NTF \l_tmpa_bool \TRUE \FALSE +% } +% \end{Verbatim} +% This test will produce the following in the output. +% \begin{Verbatim} +% ========================================== +% TEST 8: bool_set, lazy evaluation +% ========================================== +% TRUE +% ========================================== +% \end{Verbatim} +% (Only if it's the eighth test in the file of course, and assuming \pkg{expl3} +% coding conventions are active.) +% +% \subsection{Showing box content} +% +% The commands introduced above are only useful for checking algorithmic or logical correctness. +% Many packages should be tested based on their typeset output instead; \TeX{} provides a mechanism for this by printing the contents of a box to the log file. +% The |regression-test.tex| driver file sets up the relevant \TeX{} parameters to produce as much output as possible when showing box output. +% +% A plain \TeX{} example of showing box content follows. +% \begin{Verbatim}[frame=single,fontsize=\small] +% \input regression-test.tex\relax +% \START +% \setbox0=\hbox{\rm hello \it world $a=b+c$} +% \showbox0 +% \END +% \end{Verbatim} +% This produces the output shown in Figure~\ref{fig:box-log} (left side). +% It is clear that if the definitions used to typeset the material in the box changes, the log output will differ and the test will no longer pass. +% +% The equivalent test in \LaTeXe{} using \pkg{expl3} is similar. +% \begin{Verbatim}[frame=single,fontsize=\small] +% \input{regression-test.tex} +% \documentclass{article} +% \usepackage{expl3} +% \START +% \ExplSyntaxOn +% \box_new:N \l_tmp_box +% \hbox_set:Nn \l_tmp_box {hello~ \emph{world}~ $a=b+c$} +% \box_show:N \l_tmp_box +% \ExplSyntaxOff +% \END +% \end{Verbatim} +% The output from this test is shown in Figure~\ref{fig:box-log} (right side). +% There is marginal difference (mostly related to font selection and different logging settings in \LaTeX) between the plain and \pkg{expl3} versions. +% +% When examples are not self-contained enough to be typeset into boxes, it is possible to ask \TeX{} to output the entire contents of a page. +% Insert \cs{showoutput} for \LaTeX{} or set \cs{tracingoutput} positive for plain \TeX{}; ensure that the test ends with \cs{newpage} or equivalent because \TeX{} waits until the entire page is finished before outputting it. +% +% TODO: should we add something like \cs{TRACEPAGES} to be format-agnostic here? Should this perhaps even be active by default? +% +% \begin{figure} +% \hspace*{-3cm} +% \begin{BVerbatim}[fontsize=\small] +% > \box0= +% \hbox(6.94444+0.83333)x90.56589 +% .\tenrm h +% .\tenrm e +% .\tenrm l +% .\tenrm l +% .\tenrm o +% .\glue 3.33333 plus 1.66666 minus 1.11111 +% .\tenit w +% .\tenit o +% .\tenit r +% .\tenit l +% .\tenit d +% +% .\glue 3.57774 plus 1.53333 minus 1.0222 +% .\mathon +% .\teni a +% .\glue(\thickmuskip) 2.77771 plus 2.77771 +% .\tenrm = +% .\glue(\thickmuskip) 2.77771 plus 2.77771 +% .\teni b +% .\glue(\medmuskip) 2.22217 plus 1.11108 minus 2.22217 +% .\tenrm + +% .\glue(\medmuskip) 2.22217 plus 1.11108 minus 2.22217 +% .\teni c +% .\mathoff +% +% ! OK. +% l.9 \showbox0 +% +% +% \end{BVerbatim} +% \qquad +% \begin{BVerbatim}[fontsize=\small] +% > \box71= +% \hbox(6.94444+0.83333)x91.35481 +% .\OT1/cmr/m/n/10 h +% .\OT1/cmr/m/n/10 e +% .\OT1/cmr/m/n/10 l +% .\OT1/cmr/m/n/10 l +% .\OT1/cmr/m/n/10 o +% .\glue 3.33333 plus 1.66666 minus 1.11111 +% .\OT1/cmr/m/it/10 w +% .\OT1/cmr/m/it/10 o +% .\OT1/cmr/m/it/10 r +% .\OT1/cmr/m/it/10 l +% .\OT1/cmr/m/it/10 d +% .\kern 1.03334 +% .\glue 3.33333 plus 1.66666 minus 1.11111 +% .\mathon +% .\OML/cmm/m/it/10 a +% .\glue(\thickmuskip) 2.77771 plus 2.77771 +% .\OT1/cmr/m/n/10 = +% .\glue(\thickmuskip) 2.77771 plus 2.77771 +% .\OML/cmm/m/it/10 b +% .\glue(\medmuskip) 2.22217 plus 1.11108 minus 2.22217 +% .\OT1/cmr/m/n/10 + +% .\glue(\medmuskip) 2.22217 plus 1.11108 minus 2.22217 +% .\OML/cmm/m/it/10 c +% .\mathoff +% +% ! OK. +% \l_tmp_box +% +% l.12 \box_show:N \l_tmp_box +% \end{BVerbatim} +% \caption{Output from displaying the contents of a simple box to the log file, using plain \TeX{} (left) and \pkg{expl3} (right). Some blank lines have been added to the plain \TeX{} version to help with the comparison.} +% \label{fig:box-log} +% \end{figure} +% +% \subsection{Testing entire pages} +% +% There may be occasions where creating entire test pages is necessary +% to observe the test output required. That is best achieved by applying +% \cs{showoutput} and forcing a complete page to be produced, for example +% \begin{Verbatim}[frame=single,fontsize=\small] +% \input{regression-test.tex} +% \documentclass{article} +% \usepackage{expl3} +% \START +% \showoutput +% % Test content here +% \vfil\break +% \END +% \end{Verbatim} +% +% \subsection{Additional test tasks} +% +% A standard test will run the file \texttt{\meta{name}.lvt} using one +% or more engines, but will not carry out any additional processing. For +% some tests, for example bibliography generation, it may be desirable to +% call one or more tools in addition to the engine. This can be arranged +% by defining |runtest_tasks|, a function taking one argument, the name +% of the current test (this is equivalent to \TeX{}'s \cs{jobname}, +% \emph{i.e.}~it lacks an extension). The function |runtest_tasks| +% is is into a call to the system to run the engine. As such, it +% should take return a string with the appropriate command(s) +% and option(s). If more than one task is required, these should be separated +% by use of |os_concat|, a string variable defined by \pkg{l3build} as the +% correct concatenation marker for the system. An example of |runtest_tasks| +% suitable for calling Biber is shown in Listing~\ref{fig:test-tasks}. +% \begin{figure} +% \begin{lstlisting}[frame=single,language={[5.2]Lua},gobble = 6] +% function runtest_tasks(name) +% return "biber " .. name +% end +% \end{lstlisting} +% \caption{Example \texttt{runtest_tasks} function.} +% \label{fig:test-tasks} +% \end{figure} +% +% \subsection{Epoch setting} +% +% To produce predictable output when using dates, the test system offers the +% ability to set the epoch to a known value. The \luavar{epoch} variable may +% be given as a raw value (a simple integer) or as a date in ISO format. +% The two flags \luavar{forcecheckepoch} and \luavar{forcedocepoch} then +% determine whether this is applied in testing and typesetting, respectively. +% +% The epoch may also be given as a command line option, |-E|, which again +% takes either a date or raw epoch. When given, this will automatically +% activate forcing of the epoch in both testing and typesetting. +% +% \subsection{Settings in \texttt{texmf.cnf}} +% +% To allow application of non-standard \TeX{} trees or similar non-standard +% settings, \pkg{l3build} enables searching for a \texttt{texmf.cnf} file +% by setting the envirnmental variable \texttt{TEXMFCNF}. This might +% for example be used with a file containing +% \begin{verbatim} +% TEXMFAUXTREES = ../../texmf, +% \end{verbatim} +% for adding a local tree within the development repository (assuming the +% typical \pkg{l3build} layout). +% +% \section{Alternative test formats} +% +% \subsection{Generating test files with \pkg{DocStrip}} +% +% It is possible to pack tests inside source files. Tests generated during the +% unpacking process will be available to the \texttt{check} and \texttt{save} +% commands as if they were stored in the \texttt{testfiledir}. Any explicit +% test files inside \texttt{testfiledir} take priority over generated ones +% with the same names. +% +% \subsection{Specifying expectations} +% +% Regression tests check whether changes introduced in the code modify the test +% output. Especially while developing a complex package there is not yet a +% baseline to save a test goal with. It might then be easier to formulate the +% expected effects and outputs of tests directly. To achieve this, you may +% create an \texttt{.lve} instead of a \texttt{.tlg} +% file.\footnote{Mnemonic: \texttt{lv\textbf{t}}: \textbf{t}est, +% \texttt{lv\textbf{e}}: \textbf{e}xpectation} It is processed exactly like +% the \texttt{.lvt} to generate the expected outcome. The test fails when both +% differ. +% +% Combining both features enables contrasting the test with its expected +% outcome in a compact format. Listing \ref{fig:expect-dtx} exemplary tests +% \TeX{}s counters. Listing \ref{fig:expect-ins} shows the relevant part of an +% \texttt{.ins} file to generate it. +% +% \begin{figure} +% \begin{lstlisting}[frame=single,language={TeX},gobble = 6] +% \input regression-test.tex\relax +% \START +% \TEST{counter-math}{ +% %<*test> +% \OMIT +% \newcounter{numbers} +% \setcounter{numbers}{2} +% \addtocounter{numbers}{2} +% \stepcounter{numbers} +% \TIMO +% \typeout{\arabic{numbers}} +% % +% % \typeout{5} +% } +% \END +% \end{lstlisting} +% \caption{Test and expectation can be specified side-by-side in a single +% \texttt{.dtx} file.} +% \label{fig:expect-dtx} +% \end{figure} +% +%\begin{figure} +% \begin{lstlisting}[frame=single,language={TeX},gobble = 6] +% \generate{\file{\jobname.lvt}{\from{\jobname.dtx}{test}} +% \file{\jobname.lve}{\from{\jobname.dtx}{expect}}} +% \end{lstlisting} +% \caption{Test and expectation are generated from a \texttt{.dtx} file of +% the same name.} +% \label{fig:expect-ins} +% \end{figure} +% +% \subsection{PDF-based tests} +% +% In most cases, testing is best handled by using the text-based methods +% outlined above. However, there are cases where the detail of output structure +% is important. This can only be fully tested by comparing PDF structure. +% To support this, \pkg{l3build} can be instructed to build and compare +% PDF files by setting up tests in \texttt{.pvt} files. The following +% normalization takes place: +% \begin{itemize} +% \item Replacement of binary streams by the marker |[BINARY STREAM]| +% \item Removal of blank lines +% \end{itemize} +% +% After this normalization takes place, the file can not usually be rendered +% properly. To check if the build system has produced a correct PDF, the +% pre-normalization PDF can be found in the \texttt{build} folder. +% +% To allow platform-independence, PDF-based tests must use only Type~1 or +% OpenType fonts: Type3 fonts are system-dependent. PDF files are +% engine-specific, thus one |.tpf| file should be stored per engine to be +% tested. +% +% +% \section{Release-focussed features} +% +% \subsection{Installation structure} +% +% With the standard settings, \pkg{l3build} will install files within +% the \TeX{} directory structure (TDS) as follows +% \begin{itemize} +% \item \var{installfiles} within a \texttt{\meta{bundle}/\meta{module}} +% (or \texttt{\meta{module}}) directory inside \texttt{tex/\meta{format}} +% \item \var{sourcefiles} within a \texttt{\meta{bundle}/\meta{module}} +% (or \texttt{\meta{module}}) directory inside \texttt{source/\meta{format}} +% \item Typeset PDFs within a \texttt{\meta{bundle}/\meta{module}} +% (or \texttt{\meta{module}}) directory inside \texttt{doc/\meta{format}} +% \item \var{bstfiles} within a \texttt{\meta{bundle}/\meta{module}} +% (or \texttt{\meta{module}}) directory inside \texttt{bibtex/bst} +% \item \var{bibfiles} within a \texttt{\meta{bundle}/\meta{module}} +% (or \texttt{\meta{module}}) directory inside \texttt{bibtex/bib} +% \item \var{makeindexfiles} within a \texttt{\meta{bundle}/\meta{module}} +% (or \texttt{\meta{module}}) directory inside \texttt{makeindex} +% \end{itemize} +% +% For more complex set ups, this can be customised using the +% \luavar{tdslocations} table. Each entry there should be a glob specifying the +% TDS position of a file or files. Any files not specified in the table +% will use the standard locations above. For example, to place some files +% in the generic tree, some in the plain \TeX{} tree and some in the \LaTeX{} +% tree, one might use the set up shown in Figure~\ref{fig:tds}. +% \begin{figure} +% \begin{lstlisting}[frame=single,language={[5.2]Lua},gobble = 6] +% tdslocations = +% { +% "tex/generic/mypkg/*.generic.tex" , +% "tex/plain/mypkg/*.plain.tex" , +% "tex/latex/mypkg/*.latex.tex" +% } +% \end{lstlisting} +% \caption{Example \texttt{tdslocations} table.} +% \label{fig:tds} +% \end{figure} +% +% The table is read in order, and thus specific file names should come before +% potential wild-card matches. +% +% \subsection{Automatic tagging} +% \label{sec:tagging} +% +% The |tag| target can automatically edit +% source files to modify date and release tag. As standard, no automatic +% replacement takes place, but setting up a |update_tag()| function +% will allow this to happen. This function takes four input arguments: +% \begin{enumerate}[nosep] +% \item file name +% \item full content of the file +% \item tag name +% \item tag date +% \end{enumerate} +% The |update_tag()| function should return the (modified) contents +% for writing to disk. +% For example, the function used by |l3build| itself is +% shown in Figure~\ref{fig:update-tag}. +% +% \begin{figure} +% \begin{lstlisting}[frame=single,language={[5.2]Lua},gobble = 6] +% -- Detail how to set the version automatically +% function update_tag(file,content,tagname,tagdate) +% if string.match(file, "%.dtx$") then +% return string.gsub(content, +% "\n%% \\date{Released %d%d%d%d/%d%d/%d%d}\n", +% "\n%% \\date{Released " .. tagname .. "}\n") +% elseif string.match(file, "%.md$") then +% return string.gsub(content, +% "\nRelease %d%d%d%d/%d%d/%d%d\n", +% "\nRelease " .. tagname .. "\n") +% elseif string.match(file, "%.lua$") then +% return string.gsub(content, +% '\nrelease_date = "%d%d%d%d/%d%d/%d%d"\n', +% '\nrelease_date = "' .. tagname .. '"\n') +% end +% return contents +% end +% \end{lstlisting} +% \caption{Example \texttt{update_tag} function.} +% \label{fig:update-tag} +% \end{figure} +% +% To allow more complex tasks to take place, a hook |tag_hook()| is also +% available. It will receive the tag name and date as arguments, and +% may be used to carry out arbitrary tasks after the main tagging process. +% For example, this can be used to set a version control tag for an entire repository. +% +% +% \subsection{Typesetting documentation} +% +% As part of the overall build process, \pkg{l3build} will create PDF +% documentation as described earlier. The standard build process for PDFs +% will attempt to run Biber, \BibTeX{} and MakeIndex as appropriate +% (the exact binaries used are defined by \luavar{biberexe}, \luavar{bibtexexe} +% and \luavar{makeindexexe}). However, there is no attempt to create an entire +% PDF creation system in the style of \texttt{latexmk} or similar. +% +% For package authors who have more complex requirements than those covered +% by the standard set up, the Lua script offers the possibility for +% customisation. The Lua function \texttt{typeset} may be defined before +% reading \texttt{l3build.lua} and should take one argument, the name of +% the file to be typeset. Within this function, the auxiliary Lua +% functions \texttt{biber}, \texttt{bibtex}, \texttt{makeindex} and +% \texttt{tex} can be used, along with custom code, to define a PDF +% typesetting pathway. The functions \texttt{biber} and \texttt{bibtex} +% take a single argument: the name of the file to work with \emph{minus} +% any extension. The \texttt{tex} takes as an argument the full name +% of the file. The most complex function \texttt{makeindex} requires the +% name, input extension, output extension, log extension and style name. +% For example, Figure~\ref{fig:PDF} shows a simple script which might +% apply to a case where multiple \BibTeX{} runs are needed (perhaps where +% citations can appear within other references). +% +% Where there are complex requirements for pre-compiled demonstration +% files, the hook |typeset_demo_tasks()| is available: it runs after +% copying files to the typesetting location but before the main typesetting +% run. This may be used for example to script a very large number of +% demonstrations using a single source (see the \pkg{beamer} package +% for an example of this). +% +% \begin{figure}[!b] +% \begin{lstlisting}[frame=single,language={[5.2]Lua},gobble = 6] +% #!/usr/bin/env texlua +% +% -- Build script with custom PDF route +% +% module = "mymodule" +% +% function typeset(file) +% local name = jobname(file) +% local errorlevel = tex (file) +% if errorlevel == 0 then +% -- Return a non-zero errorlevel if anything goes wrong +% errorlevel =( +% bibtex(name) + +% tex(file) + +% bibtex(name) + +% tex(file) + +% tex(file) +% ) +% end +% return errorlevel +% end +% \end{lstlisting} +% \caption{A customised PDF creation script.} +% \label{fig:PDF} +% \end{figure} +% +% +% \subsection{Automated upload to CTAN} +% \label{sec:upload} +% +% The CTAN upload process is backed by an API, which \pkg{l3build} can use +% to send zip files for release. Along with the file, a variety of metadata +% must be specified about the package, including the version, license, and so on. +% A description of this metadata is outlined in Table~\ref{tab:upload-setup}, +% and a simple example of an extract from a \texttt{build.lua} file using this is shown +% in Figure~\ref{fig:uploadconfig}. +% +% Note that the \texttt{upload} target will \emph{not} execute the +% \texttt{ctan} target first. +% +% This upload facility assumes availablity of |curl| on your +% system. In the case of Windows, the system curl will not be +% available if you are using a 32 bit \TeX\ implementation. Curl +% executables are available for a variety of operating systems from +% \url{https://curl.haxx.se/download.html}. +% +% \paragraph{Announcement text} +% It can be convenient not to include the announcement text within the |build.lua| file +% directly. The command line argument |--message| (|-m|) allows the announcement to be +% included as part of the |l3build| arguments, and |--file| (|-F|) reads the announcement +% from a specified file. The \texttt{build.lua} file may also specify that this text is to +% be taken from the file specified by +% |uploadconfig.announcement_file|, this allows the release-specific announcement to be +% specified outside the main |build.lua| file. If +% |uploadconfig.announcement_file| is |nil| or specifies a file that +% can not be read, and no announcement is provided by the +% |announcement| field or commandline arguments, |l3build| will +% interactively prompt for text (which may be empty). +% +% Note that if the announcement text is empty a `silent update' +% is performed; this should usually be performed for minor bug or documentation fixes only. +% +% \paragraph{Note text} +% This optional field is for passing notes to the CTAN maintainers. As +% for announcements, the text may be set in |uploadconfig.note| or +% perhaps more usefully, if |uploadconfig.note_file| is the filename of a +% readable file the file text is used as the note. +% +% \paragraph{Uploader details} +% The CTAN team use the uploader email address as a form of low-security sanity +% check that the upload is coming from a reputable source. Therefore, it is advisable not +% to store this information within a public |build.lua| file. It can be set on the command +% line with the |--email| option to \texttt{l3build}; alternatively, a private +% configuration file could be used to add this information at upload time. +% +% \paragraph{The \texttt{update} field} +% In most scenarios the |update| field does not need to be explicitly set. By default +% \pkg{l3build} assumes that the package being uploaded already exists on CTAN +% (|update=true|). If it does not, this is caught in the validation process before +% uploading and automatically corrected. If you set |update| explicitly this will be passed +% directly to CTAN in all circumstances, leading to errors if you attempt to update a +% non-existing package or if you attempt to upload a new package with the same name as a +% pre-existing one. +% +% \paragraph{The \texttt{curl} options file} +% +% The \pkg{l3build} upload options are passed to |curl| by writing the +% fields to a text file with a default name being +% \meta{package}|-ctan.curlopt|. This is then passed to curl using its +% |--config| commandline option. (Using an intermediate file helps +% keep \pkg{l3build} portable between systems using different +% commandline quoting conventions.) +% +% By default the file is written into the current directory alongside +% the zip file to be uploaded. You may wish to specify that this file +% is ignored by any version control in that directory (using +% |.gitignore| or similar). Or alternatively you can use the +% |uploadconfig.curl_file| field in the |build.lua| file to specify an +% alternative name or location for this file. +% +% \paragraph{Debugging} +% If you have have difficulty with the upload process, add the option |--debug| to divert +% the request from CTAN to a service that redirects the input back +% again so it can be examined. +% It can also be useful to check the contents of the |curlopts| file which has a +% record of the options passed to curl. +% +% \begin{table}[p] +% \def\YES{\textbullet} +% \caption{Fields used in the \texttt{uploadconfig} setup table. The +% first section of fields are \emph{required} and if they are +% omitted the user will be interactively prompted for further +% input. Most commands take string input, but those that are +% indicated with `Multi' accept more than one entry using an array +% of strings. Most of the fields correspond directly to the fields +% in the CTAN upload API, the last group relate to file use by +% \pkg{l3build}.} +% \label{tab:upload-setup} +% \medskip +% \begin{minipage}{\linewidth} +% \begin{tabular}{@{}lccp{8cm}@{}} +% \toprule +% Field & Req. & Multi & Description \\ +% \midrule +% \texttt{announcement} & \YES & & Announcement text \\ +% \texttt{author } & \YES & & Author name (semicolon-separated for multiple) \\ +% \texttt{ctanPath } & \YES & & CTAN path \\ +% \texttt{email } & \YES & & Email address of uploader \\ +% \texttt{license } & \YES & \YES & Package license(s)\footnote{See \url{https://ctan.org/license}} \\ +% \texttt{pkg } & \YES & & Package name \\ +% \texttt{summary } & \YES & & One-line summary \\ +% \texttt{uploader } & \YES & & Name of uploader \\ +% \texttt{version } & \YES & & Package version \\ +% \midrule +% \texttt{bugtracker } & & \YES & URL(s) of bug tracker \\ +% \texttt{description } & & & Short description/abstract \\ +% \texttt{development } & & \YES & URL(s) of development channels \\ +% \texttt{home } & & \YES & URL(s) of home page \\ +% \texttt{note } & & & Internal note to CTAN \\ +% \texttt{repository } & & \YES & URL(s) of source repositories \\ +% \texttt{support } & & \YES & URL(s) of support channels \\ +% \texttt{topic } & & \YES & Topic(s)\footnote{See \url{https://ctan.org/topics/highscore}} \\ +% \texttt{update } & & & Boolean \texttt{true} for an update, \texttt{false} for a new package \\ +% \midrule +% \texttt{announcement\_file} & & & Announcement text file \\ +% \texttt{note\_file} & & & Note text file \\ +% \texttt{curlopt\_file} & & & The filename containing the options passed to curl \\ +% \bottomrule +% \end{tabular} +% \end{minipage} +% \end{table} +% +% +% \begin{figure}[p] +% \begin{lstlisting}[frame=single,language={[5.2]Lua},gobble = 6] +% uploadconfig = { +% pkg = "vertbars", +% version = "v1.0c", +% author = "Peter R Wilson; Will Robertson", +% license = "lppl1.3c", +% summary = "Mark vertical rules in margin of text", +% ctanPath = "/macros/latex/contrib/vertbars", +% repository = "https://github.com/wspr/herries-press/", +% update = true, +% } +% \end{lstlisting} +% \caption{Example of \texttt{uploadconfig} from the \pkg{vertbars} package.} +% \label{fig:uploadconfig} +% \end{figure} +% +% \section{Lua interfaces} +% +% Whilst for the majority of users the simple variable-based control methods +% outlined above will suffice, for more advanced applications there will be +% a need to adjust behavior by using interfaces within the Lua code. This +% section details the global variables and functions provided. +% +% \subsection{Global variables} +% +% \begin{variable}{options} +% The |options| table holds the values passed to \pkg{l3build} at the +% command line. The possible entries in the table are given in the table +% below. +% \begin{center} +% \begin{tabular}{ll} +% \toprule +% Entry & Type \\ +% \midrule +% \var{config} & Table \\ +% \var{date} & String \\ +% \var{dirty} & Boolean \\ +% \var{dry-run} & Boolean \\ +% \var{email} & String \\ +% \var{engine} & Table \\ +% \var{epoch} & String \\ +% \var{file} & string \\ +% \var{first} & Boolean \\ +% \var{force} & Boolean \\ +% \var{full} & Boolean \\ +% \var{halt-on-error} & Boolean \\ +% \var{help} & Boolean \\ +% \var{message} & string \\ +% \var{names} & Table \\ +% \var{quiet} & Boolean \\ +% \var{rerun} & Boolean \\ +% \var{shuffle} & Boolean \\ +% \var{texmfhome} & String \\ +% \bottomrule +% \end{tabular} +% \end{center} +% \end{variable} +% +% \subsection{Utility functions} +% +% The utility functions are largely focussed on file operations, though a small +% number of others are provided. File paths should be given in Unix style +% (using |/| as a path separator). File operations take place relative to the +% path from which \pkg{l3build} is called. File operation syntax is largely +% modelled on Unix command line commands but reflect the need to work on +% Windows in a flexible way. +% +% \begin{function}{abspath()} +% \begin{syntax} +% |abspath(|\meta{target}|)| +% \end{syntax} +% Returns a string which gives the absolute location of the +% \meta{target} directory. +% \end{function} +% +% \begin{function}{dirname()} +% \begin{syntax} +% |dirname(|\meta{file}|)| +% \end{syntax} +% Returns a string comprising the path to a \meta{file} with the name removed +% (\emph{i.e.}~up to the last |/|). Where the \meta{file} has no path data, +% |"."| is returned. +% \end{function} +% +% \begin{function}{basename()} +% \begin{syntax} +% |basename(|\meta{file}|)| +% \end{syntax} +% Returns a string comprising the full name of the \meta{file} with the +% path removed (\emph{i.e.}~from the last |/| onward). +% \end{function} +% +% \begin{function}{cleandir()} +% \begin{syntax} +% |cleandir(|\meta{dir}|)| +% \end{syntax} +% Removes any content within the \meta{dir}; returns an error level. +% \end{function} +% +% \begin{function}{cp()} +% \begin{syntax} +% |cp(|\meta{glob}, \meta{source}, \meta{destination}|)| +% \end{syntax} +% Copies files matching the \meta{glob} from the \meta{source} directory +% to the \meta{destination}; returns an error level. +% \end{function} +% +% \begin{function}{direxists()} +% \begin{syntax} +% |direxists(|\meta{dir}|)| +% \end{syntax} +% Tests if the \meta{dir} exists; returns a boolean value. +% \end{function} +% +% \begin{function}{fileexists()} +% \begin{syntax} +% |fileexists(|\meta{file}|)| +% \end{syntax} +% Tests if the \meta{file} exists; returns a boolean value. +% \end{function} +% +% \begin{function}{filelist()} +% \begin{syntax} +% |filelist(|\meta{path}, \oarg{glob}|)| +% \end{syntax} +% Returns a table containing all of the files with the \meta{path} +% which match the \meta{glob}; if the latter is absent returns a list of +% all files in the \meta{path}. +% \end{function} +% +% \begin{function}{glob_to_pattern()} +% \begin{syntax} +% |glob_to_pattern(|\meta{glob}|)| +% \end{syntax} +% Returns the \meta{glob} converted to a Lua pattern. +% \end{function} +% +% \begin{function}{jobname()} +% \begin{syntax} +% |jobname(|\meta{file}|)| +% \end{syntax} +% Returns a string comprising the jobname of the file with the +% path and extension removed (\emph{i.e.}~from the last |/| up to the +% last |.|). +% \end{function} +% +% \begin{function}{mkdir()} +% \begin{syntax} +% |mkdir(|\meta{dir}|)| +% \end{syntax} +% Creates the \meta{dir}; returns an error level. +% \end{function} +% +% \begin{function}{ren()} +% \begin{syntax} +% |ren(|\meta{dir}, \meta{source}, \meta{destination}|)| +% \end{syntax} +% Renames the \meta{source} file to the \meta{destination} name within +% the \meta{dir}; returns an error level. +% \end{function} +% +% \begin{function}{rm()} +% \begin{syntax} +% |rm(|\meta{dir}, \meta{glob}|)| +% \end{syntax} +% Removes files in the \meta{dir} matching the \meta{glob}; returns an +% error level. +% \end{function} +% +% \begin{function}{run()} +% \begin{syntax} +% |run(|\meta{dir}, \meta{cmd}|)| +% \end{syntax} +% Executes the \meta{cmd}, starting it in the \meta{dir}; returns an +% error level. +% \end{function} +% +% \begin{function}{splitpath()} +% \begin{syntax} +% |splitpath(|\meta{file}|)| +% \end{syntax} +% Returns two strings split at the last |/|: the \texttt{dirname()} and +% the |basename()|. +% \end{function} +% +% \begin{function}{normalize_path()} +% \begin{syntax} +% |normalize_path(|\meta{path}|)| +% \end{syntax} +% When called on Windows, returns a string comprising the \meta{path} with +% |/| characters replaced by |\\|. In other cases returns the path unchanged. +% \end{function} +% +% \subsection{System-dependent strings} +% +% To support creation of additional functionality, the following low-level +% strings are exposed by \pkg{l3build}: these all have system-dependent +% definitions and avoid the need to test |os.type| during the construction +% of system calls. +% +% \begin{variable}{os_concat} +% The concatenation operation for using multiple commands in one +% system call, \emph{e.g.} +% \begin{verbatim} +% os.execute("tex " .. file .. os_concat .. "tex " .. file) +% \end{verbatim} +% \end{variable} +% +% \begin{variable}{os_null} +% The location to redirect commands which should produce no output +% at the terminal: almost always used preceded by |>|, \emph{e.g.} +% \begin{verbatim} +% os.execute("tex " .. file .. " > " .. os_null) +% \end{verbatim} +% \end{variable} +% +% \begin{variable}{os_pathsep} +% The separator used when setting an environment variable to multiple +% paths, \emph{e.g.} +% \begin{verbatim} +% os.execute(os_setenv .. " PATH=../a" .. os_pathsep .. "../b") +% \end{verbatim} +% \end{variable} +% +% \begin{variable}{os_setenv} +% The command to set an environmental variable, \emph{e.g.} +% \begin{verbatim} +% os.execute(os_setenv .. " PATH=../a") +% \end{verbatim} +% \end{variable} +% +% \begin{variable}{os_yes} +% A command to generate a series of $200$ lines each containing the +% character |y|: this is useful as the Unix |yes| command cannot be +% used inside |os.execute| (it does not terminate). +% \end{variable} +% +% \subsection{Components of \texttt{l3build}} +% +% \begin{function}{call()} +% \begin{syntax} +% |call(|\meta{dirs}, \meta{target}, \oarg{options}|)| +% \end{syntax} +% Runs the \texttt{l3build} \meta{target} (a string) for each directory in the +% \meta{dirs} (a table). This will pass command line options for the parent +% script to the child processes. The \meta{options} table should take the +% same form as the global \meta{options}, described above. If it is +% absent then the global list is used. Note that any entry for the +% |target| in this table is ignored. +% \end{function} +% +% \begin{function}{install_files()} +% \begin{syntax} +% |install_files(|\meta{target},\meta{full},\meta{dry-run}|)| +% \end{syntax} +% Installs the files from the module into the TDS root \meta{target}. +% If \meta{full} is \texttt{true}, all files are copied: if it is +% \texttt{false}, the \texttt{doc} and \texttt{source} trees are skipped. +% If \meta{dry-run} is \texttt{true}, no files are copied, but instead the +% files which would be copied are reported. +% \end{function} +% +% \subsection{Typesetting functions} +% +% All typetting functions return $0$ on a successful completion. +% +% \begin{function}{biber()} +% \begin{syntax} +% |biber(|\meta{name},\meta{dir}|)| +% \end{syntax} +% Runs Biber on the \meta{name} (\emph{i.e.}~a jobname lacking any +% extension) inside the \meta{dir}. If there is no |.bcf| file then +% no action is taken with a return value of $0$. +% \end{function} +% +% \begin{function}{bibtex()} +% \begin{syntax} +% |bibtex(|\meta{name},\meta{dir}|)| +% \end{syntax} +% Runs Bib\TeX{} on the \meta{name} (\emph{i.e.}~a jobname lacking any +% extension) inside the \meta{dir}. If there are no |\citation| lines in +% the |.aux| file then no action is taken with a return value of $0$. +% \end{function} +% +% \begin{function}{makeindex()} +% \begin{syntax} +% |makeindex(|\meta{name},\meta{dir},\meta{inext},\meta{outext},\meta{logext},\meta{style}|)| +% \end{syntax} +% Runs MakeIndex on the \meta{name} (\emph{i.e.}~a jobname lacking any +% extension) inside the \meta{dir}. The various extensions and the \meta{style} +% should normally be given as it standard for MakeIndex. +% \end{function} +% +% \begin{function}{tex()} +% \begin{syntax} +% |tex(|\meta{file},\meta{dir}|)| +% \end{syntax} +% Runs \luavar{typesetexe} on the \meta{name} inside the \meta{dir}. +% \end{function} +% +% \begin{function}{runcmd()} +% \begin{syntax} +% |runcmd(|\meta{cmd},\meta{dir},|{|\meta{envvars}|})| +% \end{syntax} +% A generic function which runs the \meta{cmd} in the \meta{dir}, first +% setting up all of the environmental variables specified to +% point to the |local| and |working| directories. This function is useful +% when creating non-standard typesetting steps. +% \end{function} +% +% \subsection{Customising the target list} +% +% The targets known to \pkg{l3build} are stored in the global table +% |target_list|. Each entry should have at least a |func|, pointing to the +% function used to implement the target. This function will receive the +% list of names given at the command line as a table argument. +% In most cases, targets will also have a |desc|, used to construct |help()| +% automatically. In addition, the following may also be used: +% \begin{itemize} +% \item |bundle_func| A variant of |func| used when at the top level of +% a bundle +% \item |bundle_target| A boolean to specify that when passing the target +% name in a bundle, it should have |bundle| prepended. +% \item |pre| A function executed before the main function, and receiving +% the |names| as an argument; this allows checking of the |name| data +% without impact on the main |func|. +% \end{itemize} +% +% \subsection{Customising the manifest file} +% \label{sec:manifest} +% +% The default setup for the manifest file creating with the \texttt{manifest} +% target attempt to reflect the defaults for \pkg{l3build} itself. +% The groups (and hence the files) displayed can be completely +% customised by defining a new setup function which creates a Lua table with +% the appropriate settings (\S\ref{sec:manifest-groups}). +% +% The formatting within the manifest file can be customised by redefining a number +% of Lua functions. This includes +% how the files are sorted within each group (\S\ref{sec:manifest-sorting}), +% the inclusion of one-line descriptions for each file (\S\ref{sec:manifest-desc}), +% and the details of the formatting of each entry (\S\ref{sec:manifest-formatting}). +% +% To perform such customisations, either include the re-definitions directly within your +% package's |build.lua| file, or make a copy of |l3build-manifest-setup.lua|, rename it, +% and load it within your |build.lua| using |dofile()|. +% +% +% \subsubsection{Custom manifest groups} +% \label{sec:manifest-groups} +% +% The setup code for defining each group of files within the manifest looks something like +% the following: +% \begin{verbatim} +% manifest_setup = function() +% local groups = { +% { +% subheading = "Repository files", +% description = [[ +% Files located in the package development repository. +% ]], +% }, +% { +% name = "Source files", +% description = [[ +% These are source files generating the package files. +% ]], +% files = {sourcefiles}, +% }, +% { +% name = "Typeset documentation source files", +% description = [[ +% These files are typeset using LaTeX to produce the PDF documentation for the package. +% ]], +% files = {typesetfiles,typesetsourcefiles,typesetdemofiles}, +% }, +% ... +% } +% return groups +% end +% \end{verbatim} +% +% The |groups| variable is an ordered array of tables which contain the metadata about each +% `group' in the manifest listing. +% The keys supported in these tables are outlined in Table~\ref{tab:manifest-setup} and Table~\ref{tab:manifest-subheadings} +% See the complete setup code in |l3build-manifest-setup.lua| for examples of these in use. +% +% \begin{table} +% \caption{Table entries used in the manifest setup table for a group.} +% \label{tab:manifest-setup} +% \centering +% \begin{tabular}{lp{8cm}} +% \toprule +% Entry & Description \\ +% \midrule +% \var{name} & The heading of the group \\ +% \var{description} & The description printed below the heading \\ +% \var{files} & Files to include in this group \\ +% \var{exclude} & Files to exclude (default |{excludefiles}|) \\ +% \var{dir} & The directory to search (default |maindir|) \\ +% \var{rename} & An array with a |gsub| redefinition for the filename \\ +% \var{skipfiledescription} & Whether to extract file descriptions from these files (default |false|) \\ +% \bottomrule +% \end{tabular} +% \end{table} +% +% \begin{table} +% \caption{Table entries used in the manifest setup table for a subheading.} +% \label{tab:manifest-subheadings} +% \centering +% \begin{tabular}{lp{8cm}} +% \toprule +% Entry & Description \\ +% \midrule +% \var{subheading} & The subheading \\ +% \var{description} & The description printed below the subheading \\ +% \bottomrule +% \end{tabular} +% \end{table} +% +% +% \subsubsection{Sorting within each manifest group} +% \label{sec:manifest-sorting} +% +% Within a single group in the manifest listing, files can be matched against multiple variables. +% For example, for |sourcefiles={*.dtx,*.ins}| the following (unsorted) file listing might result: +% \begin{itemize}[nosep] +% \item foo.dtx +% \item bar.dtx +% \item foo.ins +% \item bar.ins +% \end{itemize} +% This listing can be sorted using two separate functions by the default manifest code. +% The first, default, is to sort alphabetically within a single variable match. +% This keeps all files of a single extension contiguous in the listing. +% To edit how this sort is performed, redefine the |manifest_sort_within_match| function. +% +% The second approach to sorting is to apply a sorting function to the entire set of matched files. +% (This happens \emph{after} any sorting is applied for each match.) +% By default this is a no-op but can be edited by redefining the |manifest_sort_within_group| +% function. For example: +% \begin{verbatim} +% manifest_sort_within_group = function(files) +% local f = files +% table.sort(f) +% return f +% end +% \end{verbatim} +% This will produce an alphabetical listing of files: +% \begin{itemize} +% \item bar.dtx +% \item bar.ins +% \item foo.dtx +% \item foo.ins +% \end{itemize} +% +% +% \subsubsection{File descriptions} +% \label{sec:manifest-desc} +% +% By default the manifest contains lists of files, and with a small addition these +% lists can be augmented with a one-line summary of each file. +% If the Lua function |manifest_extract_filedesc| is defined, it will be used to search +% the contents of each file to extract a description for that file. +% For example, perhaps you are using multiple |.dtx| files for a project and the argument +% to the first |\section| in each can be used as a file description: +% \begin{verbatim} +% manifest_extract_filedesc = function(filehandle,filename) +% +% local all_file = filehandle:read("*all") +% local matchstr = "\\section{(.-)}" +% +% filedesc = string.match(all_file,matchstr) +% +% return filedesc +% end +% \end{verbatim} +% (Note the |matchstr| above is only an example and doesn't handle nested braces.) +% +% +% \subsubsection{Custom formatting} +% \label{sec:manifest-formatting} +% +% After the manifest code has built a complete listing of files to print, a series of +% file writing operations are performed which create the manifest file. +% The following functions can be re-defined to change the formatting of the manifest file: +% \begin{itemize} +% \item |manifest_write_opening|: Write the heading of the manifest file and its opening paragraph. +% \item |manifest_write_subheading|: Write a subheading and description +% \item |manifest_write_group_heading|: Write the section heading of the manifest group and the group description +% \item |manifest_write_group_file|: Write the filename (when not writing file descriptions) +% \item |manifest_write_group_file_descr|: Write the filename and the file description +% \end{itemize} +% Full descriptions of their usage and arguments can be found within the |l3build-manifest-setup.lua| +% code itself. +% +% +% \end{documentation} +% +% \begin{implementation} +% +% \clearpage +% \section{\texttt{regression-test.tex}} +% +% This section describes the code for setting up the regression test system. +% Each test file should start with |\input regression-test.tex\relax|. +% +% \begin{macrocode} +%<*package> +% \end{macrocode} +% +% \subsection{Preliminaries} +% +% We require \eTeX{}. +% \begin{macrocode} +\begingroup\expandafter\expandafter\expandafter\endgroup +\expandafter\ifx\csname eTeXversion\endcsname\relax + \errmessage{e-TeX is required to use regression-test.tex}% + \expandafter\endinput +\fi +% \end{macrocode} +% +% \begin{macro}{\reset@catcodes} +% Unlike in the \LaTeXe{} regression test suite, reset catcodes: each test +% should set these as appropriate. There's also a quick test for Con\TeX{}t: +% it defines |\unprotect| which should be a reasonable marker (and is +% needed as some catcodes are otherwise wrong). +% \begin{macrocode} +\ifx\unprotect\undefined + \expandafter\edef\csname reset\string @catcodes\endcsname{% + \catcode`\noexpand\@=\the\catcode`\@\relax + }% + \catcode`\@=11 % +\else + \unprotect + \def\reset@catcodes{\protect}% +\fi +% \end{macrocode} +% \end{macro} +% +% Put \TeX{} into scroll mode, and stop it showing the +% implementation details of macros in error messages. +% \begin{macrocode} +\ifnum\interactionmode>1 \scrollmode\fi +\errorcontextlines=-1 % +% \end{macrocode} +% +% Show all box details: this avoids getting variable results if boxes +% have different numbers of lines (\LuaTeX{} adds extra information). +% \begin{macrocode} +\showboxbreadth=\maxdimen +\showboxdepth=\maxdimen +% \end{macrocode} +% +% \begin{macro}{\logginoutput} +% The |\showoutput| command of \LaTeX{} calls |\loggingoutput| which in turn +% calls |\errorstopmode| but we want to continue running without any stops. +% \begin{macrocode} +\def\loggingoutput{% + \tracingoutput=1 % + \showboxbreadth=\maxdimen + \showboxdepth=\maxdimen +} +% \end{macrocode} +% \end{macro} +% +% Set the newline character: \LaTeXe{} does this but plain-based formats do +% not. +% \begin{macrocode} +\newlinechar=`\^^J +% \end{macrocode} +% +% \subsection{Commands in test files} +% +% \begin{macro}{\LONGTYPEOUT, \TYPE} +% A long version of |\typeout|, because tests may contain |\par| tokens. +% Besides, with that |\TYPE|, we can do +% |\TYPE { ... \TRUE ... \NEWLINE ... }|. +% \begin{macrocode} +\long\def\LONGTYPEOUT#1{% + \begingroup + \long\def\TYPE##1{##1}% + \immediate\write128{#1}% + \endgroup +} +\let\TYPE\LONGTYPEOUT +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\STARTMESSAGE, \START} +% Start the test, after the optional |\documentclass| +% |\begin{document}| commands with |\START|. All lines in the |.log| file +% before this will be ignored. +% \begin{macrocode} +\def\STARTMESSAGE{This is a generated file for the l3build validation system.} +\def\START{% + \LONGTYPEOUT{^^JSTART-TEST-LOG^^J}% + \LONGTYPEOUT{^^J% + \STARTMESSAGE% + ^^J^^JDon't change this file in any respect.% + ^^J^^J% + }% +} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\@@@end, \END} +% The test should end with |\END| or |\end{document}| +% or |\bye| in plain \TeX{}. +% \begin{macrocode} +\ifx\@@end\@undefined + \let\@@@end\end +\else + \let\@@@end\@@end +\fi +\def\END{% + \ifnum\currentgrouplevel>0 % + \LONGTYPEOUT{Bad grouping: \the\currentgrouplevel!}% + \fi + \ifnum\currentiflevel>2 % + \LONGTYPEOUT{Bad conditionals: \the\numexpr\currentiflevel-2!}% + \fi + \LONGTYPEOUT{^^JEND-TEST-LOG^^J}% + \@@@end +} +\ifx\@@end\@undefined + \let\end\END +\else + \let\@@end\END +\fi +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\OMIT, \TIMO} +% Surround commands which produce irrelevant lines in the |.log| file by +% |\OMIT|\dots|\TIMO| +% \begin{macrocode} +\def\OMIT{\LONGTYPEOUT{OMIT}} +\def\TIMO{\LONGTYPEOUT{TIMO}} +% \end{macrocode} +% \end{macro} +% +% + +% \begin{macro}{\SHOWFILE} +% Load a file (e.g.,| \jobname.toc|) into the .log file with the usual special +% characters rendered harmless. Use as |\SHOWFILE{\jobname.aux}|. +% \begin{macrocode} +\begingroup % within the scope of this groups each line needs to end in % ! +\catcode`\^^M\active % +\gdef\SHOWFILE#1{% + \typeout{-------- #1 (start) ---------}% +\IfFileExists{#1}% + {\begingroup % + \catcode`\^^M\active % + \edef^^M{^\string^M^^J}% + \everyeof{\noexpand}% + \obeyspaces % + \@sanitize % + \message{\@@input #1 }% + \endgroup }% + {\message{Not found}}% + \typeout{-------- #1 (end) -----------}% +}% +\endgroup +% \end{macrocode} +% \end{macro} +% +% To allow testing of possible changes, we allow extra code to be read +% in before the test starts. The necessary code should be placed in a +% file |regression-test.cfg|. +% \begin{macrocode} +\ifx\InputIfFileExists\@undefined + \newread\@inputcheck + \long\def\InputIfFileExists#1#2#3{% + \openin\@inputcheck#1\relax + \ifeof\@inputcheck + \def\reserved@a{#3}% + \else + \def\reserved@a{#2\input #1\relax}% + \fi + \closein\@inputcheck + \reserved@a + }% +\fi +\InputIfFileExists{regression-test.cfg} + {\LONGTYPEOUT{^^J***^^Jregression-test.cfg in operation^^J***^^J}}{} +% \end{macrocode} +% +% \subsection{Formatting the \texttt{.log} file} +% +% \begin{macro}{\gTESTint} +% For tracking the total number of tests. +% \begin{macrocode} +\newcount\gTESTint +% \end{macrocode} +% \end{macro} +% +% \begin{macro} +% { +% \SEPARATOR, +% \BEGINTEST, +% \ENDTEST, +% \TEST, +% \TESTEXP, +% \TRUE, +% \FALSE, +% \YES, +% \NO, +% \NEWLINE +% } +% We are not starved for space in the log file output, so let's make it as +% verbose as is useful when reading the |.diff|'s. +% \begin{macrocode} +\def\SEPARATOR{% + \TYPE{% + ============================================================% + }% +} +\protected\long\def\BEGINTEST#1{% + \global\advance\gTESTint by 1 % + \SEPARATOR + \LONGTYPEOUT{TEST \the\gTESTint: \detokenize{#1}}% + \SEPARATOR + \begingroup + \let\TYPE\LONGTYPEOUT +} +\protected\long\def\ENDTEST{% + \endgroup + \SEPARATOR + \LONGTYPEOUT{}% +} +\long\def\TEST#1#2{% + \BEGINTEST{#1}% + #2% + \ENDTEST +} +\long\def\TESTEXP#1#2{% + \BEGINTEST{#1}% + \LONGTYPEOUT{#2}% + \ENDTEST +} +\def \TRUE {\TYPE{TRUE}} +\def \FALSE {\TYPE{FALSE}} +\def \YES {\TYPE{YES}} +\def \NO {\TYPE{NO}} +\def \NEWLINE {\TYPE{^^J}} +% \end{macrocode} +% \end{macro} +% +% \subsection{Suppressing variable data in output} +% +% Load the map file early so it does not appear in the log. +% \begin{macrocode} +\ifx\pdfoutput\@undefined + \ifx\outputmode\@undefined + \else + \ifnum\outputmode>0 % + \pdfextension mapfile{pdftex.map}% + \fi + \fi +\else + \ifnum\pdfoutput>0 % + \pdfmapfile{pdftex.map}% + \fi +\fi +% \end{macrocode} +% +% To make any PDF file produced comparable we need to suppress various +% pieces of data. This works in concert with setting the epoch from the +% environment side (as not all output can be controlled here). We are +% somewhat stuck if \texttt{dvips} is being used so just hope for the best! +% There is a limit to what can be done with the underlying PDF structure so +% there is no point entirely suppressing \texttt{Producer}: simply avoid +% any version numbers. +% \begin{macrocode} +\ifnum 0% + \ifx\pdfoutput\@undefined\else\ifnum\pdfoutput>0 1\fi\fi + \ifx\outputmode\@undefined\else\ifnum\outputmode>0 1\fi\fi + >0 % + \ifx\pdfvariable\@undefined + \pdfinfo{/Producer (\ifx\directlua\@undefined pdf\else Lua\fi TeX)} + \ifx\pdfinfoomitdate\@undefined\else + \pdfinfoomitdate = 1 % + \pdfsuppressptexinfo = \numexpr + 0 + + 1 % PTEX.Fullbanner + + 2 % PTEX.FileName + \relax + \pdftrailerid{} + \fi + \else + \pdfextension info{/Producer (LuaTeX)} + \pdfvariable suppressoptionalinfo \numexpr + 0 + + 1 % PTEX.Fullbanner + + 2 % PTEX.FileName + + 32 % CreationDate + + 64 % ModDate + + 512 % ID + \relax + \fi +\else + \ifnum 0% + \ifx\XeTeXversion\@undefined\else 1\fi + \ifx\kanjiskip\@undefined\else 1\fi + >0 % + \special{% + pdf: docinfo + << + /Creator (TeX) + /CreationDate () + /ModDate () + /Producer (\ifx\XeTeXversion\@undefined\else x\fi dvipdfmx) + >> + } + \fi +\fi +% \end{macrocode} +% +% Disable compression in PDF output. +% \begin{macrocode} +\ifnum 0% + \ifx\pdfoutput\@undefined\else\ifnum\pdfoutput>0 1\fi\fi + \ifx\outputmode\@undefined\else\ifnum\outputmode>0 1\fi\fi + >0 % + \ifx\pdfvariable\@undefined + \pdfcompresslevel=0 % + \pdfobjcompresslevel=0 % + \else + \pdfvariable compresslevel=0 % + \pdfvariable objcompresslevel=0 % + \fi +\else + \ifnum 0% + \ifx\XeTeXversion\@undefined\else 1\fi + \ifx\kanjiskip\@undefined\else 1\fi + >0 % + \special{dvipdfmx:config z 0}% Compress level + \special{dvipdfmx:config C 0x40}% Object compression + \fi +\fi +% \end{macrocode} +% +% Finish up. +% \begin{macrocode} +\reset@catcodes +% \end{macrocode} +% +% \begin{macrocode} +% +% \end{macrocode} +% +% \newpage +% \section{\texttt{l3build.lua}} +% +% This section consists of the |l3build.lua| code. +% This code is \emph{not} generated from |l3build.dtx| since it needs to already be extracted to build \pkg{l3build} itself! +% As Frank says, we don't want to end up with a double M\"unchhausen. +% +% \lstinputlisting +% [ +% basicstyle=\ttfamily\scriptsize, +% numbers=left, +% numberstyle={\tiny\color[gray]{0.4}}, +% language={[5.2]Lua}, +% procnamekeys=function, +% procnamestyle=\color{red}, +% ] +% {l3build.lua} +% +% \end{implementation} +% +% \PrintIndex diff --git a/macros/latex/contrib/l3build/l3build.ins b/macros/latex/contrib/l3build/l3build.ins new file mode 100644 index 0000000000..483bab9d4a --- /dev/null +++ b/macros/latex/contrib/l3build/l3build.ins @@ -0,0 +1,56 @@ +\iffalse meta-comment + +File l3build.ins Copyright (C) 2014-2018 The LaTeX3 Project + +It may be distributed and/or modified under the conditions of the +LaTeX Project Public License (LPPL), either version 1.3c of this +license or (at your option) any later version. The latest version +of this license is in the file + + http://www.latex-project.org/lppl.txt + +This file is part of the "l3build bundle" (The Work in LPPL) +and all files in that bundle must be distributed together. + +----------------------------------------------------------------------- + +The development version of the bundle can be found at + + https://github.com/latex3/latex3 + +for those people who are interested. + +----------------------------------------------------------------------- + +Any modification of this file should ensure that the copyright and +license information is placed in the derived files. + +\fi + +\input docstrip.tex +\askforoverwritefalse + +\preamble + +Copyright (C) 2014-2018 The LaTeX3 Project + +It may be distributed and/or modified under the conditions of +the LaTeX Project Public License (LPPL), either version 1.3c of +this license or (at your option) any later version. The latest +version of this license is in the file: + + http://www.latex-project.org/lppl.txt + +This file is part of the "l3build bundle" (The Work in LPPL) +and all files in that bundle must be distributed together. + +\endpreamble +% stop docstrip adding \endinput +\postamble +\endpostamble + +\keepsilent + +\generate{\file{regression-test.tex}{\from{l3build.dtx}{package}}} + +\endbatchfile diff --git a/macros/latex/contrib/l3build/l3build.lua b/macros/latex/contrib/l3build/l3build.lua new file mode 100644 index 0000000000..627778ef22 --- /dev/null +++ b/macros/latex/contrib/l3build/l3build.lua @@ -0,0 +1,189 @@ +#!/usr/bin/env texlua + +--[[ + +File l3build.lua Copyright (C) 2014-2018 The LaTeX3 Project + +It may be distributed and/or modified under the conditions of the +LaTeX Project Public License (LPPL), either version 1.3c of this +license or (at your option) any later version. The latest version +of this license is in the file + + http://www.latex-project.org/lppl.txt + +This file is part of the "l3build bundle" (The Work in LPPL) +and all files in that bundle must be distributed together. + +----------------------------------------------------------------------- + +The development version of the bundle can be found at + + https://github.com/latex3/l3build + +for those people who are interested. + +--]] + +-- Version information +release_date = "2019-08-24" + +-- File operations are aided by the LuaFileSystem module +local lfs = require("lfs") + +-- Local access to functions + +local assert = assert +local ipairs = ipairs +local insert = table.insert +local lookup = kpse.lookup +local match = string.match +local gsub = string.gsub +local next = next +local print = print +local select = select +local tonumber = tonumber +local exit = os.exit + +-- l3build setup and functions +kpse.set_program_name("kpsewhich") +build_kpse_path = match(lookup("l3build.lua"),"(.*[/])") +local function build_require(s) + require(lookup("l3build-"..s..".lua", { path = build_kpse_path } ) ) +end + +-- Minimal code to do basic checks +build_require("arguments") +build_require("help") + +build_require("file-functions") +build_require("typesetting") +build_require("aux") +build_require("clean") +build_require("check") +build_require("ctan") +build_require("install") +build_require("unpack") +build_require("manifest") +build_require("manifest-setup") +build_require("tagging") +build_require("upload") +build_require("stdmain") + +-- This has to come after stdmain(), +-- and that has to come after the functions are defined +if options["target"] == "help" then + help() + exit(0) +elseif options["target"] == "version" then + version() + exit(0) +end + +-- Allow main function to be disabled 'higher up' +main = main or stdmain + +-- Load configuration file if running as a script +if match(arg[0], "l3build$") or match(arg[0], "l3build%.lua$") then + -- Look for some configuration details + if fileexists("build.lua") then + dofile("build.lua") + else + print("Error: Cannot find configuration build.lua") + exit(1) + end +end + +-- Load standard settings for variables: +-- comes after any user versions +build_require("variables") + +-- Ensure that directories are 'space safe' +maindir = escapepath(maindir) +docfiledir = escapepath(docfiledir) +sourcefiledir = escapepath(sourcefiledir) +supportdir = escapepath(supportdir) +testfiledir = escapepath(testfiledir) +testsuppdir = escapepath(testsuppdir) +builddir = escapepath(builddir) +distribdir = escapepath(distribdir) +localdir = escapepath(localdir) +testdir = escapepath(testdir) +typesetdir = escapepath(typesetdir) +unpackdir = escapepath(unpackdir) + +-- Tidy up the epoch setting +-- Force an epoch if set at the command line +-- Must be done after loading variables, etc. +if options["epoch"] then + epoch = options["epoch"] + forcecheckepoch = true + forcedocepoch = true +end +normalise_epoch() + +-- Sanity check +check_engines() + +-- +-- Deal with multiple configs for tests +-- + +-- When we have specific files to deal with, only use explicit configs +-- (or just the std one) +if options["names"] then + checkconfigs = options["config"] or {stdconfig} +else + checkconfigs = options["config"] or checkconfigs +end + +if options["target"] == "check" then + if #checkconfigs > 1 then + local errorlevel = 0 + local opts = options + local failed = { } + for i = 1, #checkconfigs do + opts["config"] = {checkconfigs[i]} + errorlevel = call({"."}, "check", opts) + if errorlevel ~= 0 then + if options["halt-on-error"] then + exit(1) + else + insert(failed,checkconfigs[i]) + end + end + end + if next(failed) then + for _,config in ipairs(failed) do + print("Failed tests for configuration " .. config .. ":") + print("\n Check failed with difference files") + local testdir = testdir + if config ~= "build" then + testdir = testdir .. "-" .. config + end + for _,i in ipairs(filelist(testdir,"*" .. os_diffext)) do + print(" - " .. testdir .. "/" .. i) + end + print("") + end + exit(1) + else + -- Avoid running the 'main' set of tests twice + exit(0) + end + end +end +if #checkconfigs == 1 and + checkconfigs[1] ~= "build" and + (options["target"] == "check" or options["target"] == "save") then + local config = "./" .. gsub(checkconfigs[1],".lua$","") .. ".lua" + if fileexists(config) then + dofile(config) + testdir = testdir .. "-" .. checkconfigs[1] + else + print("Error: Cannot find configuration " .. checkconfigs[1]) + exit(1) + end +end + +-- Call the main function +main(options["target"], options["names"]) diff --git a/macros/latex/contrib/l3build/l3build.pdf b/macros/latex/contrib/l3build/l3build.pdf new file mode 100644 index 0000000000..41ff401a00 Binary files /dev/null and b/macros/latex/contrib/l3build/l3build.pdf differ -- cgit v1.2.3