From 082786bf79b2672114c5d14dc22d91ba5a90923b Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 21 Dec 2025 19:37:15 -0500 Subject: More sensible REPL-like behavior if run without any args --- CHANGES.md | 4 ++++ src/tomo.c | 26 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 0393cecf..e609509b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Version History +## v2025-12-21.6 + +- Add smarter default behavior if run without any args (REPL-like script runner) + ## v2025-12-21.5 - Various fixes for versioning and builds. diff --git a/src/tomo.c b/src/tomo.c index 1517259e..5ebd6663 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -397,6 +397,32 @@ int main(int argc, char *argv[]) { run_files = normalize_tm_paths(run_files); + if (run_files.length == 0 && format_files.length == 0 && format_files_inplace.length == 0 && parse_files.length == 0 + && transpile_files.length == 0 && compile_objects.length == 0 && compile_executables.length == 0 + && run_files.length == 0 && uninstall_libraries.length == 0 && libraries.length == 0) { + Path_t path = Path$from_str(String("~/.local/tomo/state/tomo@", TOMO_VERSION, "/run.tm")); + path = Path$expand_home(path); + Path$create_directory(Path$parent(path), 0755, true); + if (!Path$exists(path)) { + Path$write(path, + Text("# This is a handy Tomo REPL-like runner\n" // + "# Normally you would run `tomo ./file.tm` to run a script\n" // + "# See `tomo --help` for full usage\n" // + "\n" // + "func main()\n" // + " # Put your code here:\n" // + " pass\n" // + "\n" // + "# Save and exit to run\n"), + 0644); + } + List$insert(&run_files, &path, I(0), sizeof(path)); + const char *editor = getenv("EDITOR"); + if (!editor || editor[0] == '\0') editor = "vim"; + int status = system(String(editor, " ", path)); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) return 1; + } + // Compile runnable files in parallel, then execute in serial: for (int64_t i = 0; i < (int64_t)run_files.length; i++) { Path_t path = *(Path_t *)(run_files.data + i * run_files.stride); -- cgit v1.2.3 From 9deadfa357eaf5313621fca54c633a190f4a788d Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 21 Dec 2025 19:49:28 -0500 Subject: Fix performance regression on makefile --- Makefile | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 95384d50..5eba8e4e 100644 --- a/Makefile +++ b/Makefile @@ -18,14 +18,24 @@ else include config.mk # Modified progress counter based on: https://stackoverflow.com/a/35320895 +# Only run counter if we're actually building (not for phony targets or no-ops) ifndef NO_PROGRESS ifndef ECHO -T := $(shell $(MAKE) ECHO="COUNTTHIS" $(MAKECMDGOALS) --no-print-directory \ - -n | grep -c "COUNTTHIS") +# Only count if building actual files, not just checking +ifneq ($(filter build all,$(MAKECMDGOALS)),) +T := $(shell $(MAKE) ECHO="COUNTTHIS" $(filter-out check-c-compiler check-libs,$(MAKECMDGOALS)) --no-print-directory \ + -nq 2>/dev/null | grep -c "COUNTTHIS") +ifeq ($(T),0) +ECHO = echo +else N := x C = $(words $N)$(eval N := x $N) ECHO = echo -e "[`expr $C '*' 100 / $T`%]" endif +else +ECHO = echo +endif +endif endif ifndef ECHO ECHO = echo @@ -82,7 +92,7 @@ G=-ggdb O=-O3 # Note: older versions of Make have buggy behavior with hash marks inside strings, so this ugly code is necessary: TOMO_VERSION=$(shell awk 'BEGIN{hashes=sprintf("%c%c",35,35)} $$1==hashes {print $$2; exit}' CHANGES.md) -GIT_VERSION=$(shell git log -1 --pretty=format:"%as_%h") +GIT_VERSION=$(shell git log -1 --pretty=format:"%as_%h" 2>/dev/null || echo "unknown") CFLAGS=$(CCONFIG) $(INCLUDE_DIRS) $(EXTRA) $(CWARN) $(G) $(O) $(OSFLAGS) $(LTO) \ -DSUDO='"$(SUDO)"' -DDEFAULT_C_COMPILER='"$(DEFAULT_C_COMPILER)"' \ -DGIT_VERSION='"$(GIT_VERSION)"' @@ -145,8 +155,10 @@ $(BUILD_DIR)/include/tomo@$(TOMO_VERSION)%.h: src/stdlib/%.h | $(BUILD_DIR)/incl # Rule for gzipping man pages $(BUILD_DIR)/man/man1/%.gz: man/man1/% | $(BUILD_DIR)/man/man1 + @$(ECHO) Gzipping manpage $< gzip -c $< > $@ $(BUILD_DIR)/man/man3/%.gz: man/man3/% | $(BUILD_DIR)/man/man3 + @$(ECHO) Gzipping manpage $< gzip -c $< > $@ $(BUILD_DIR)/bin/tomo: $(BUILD_DIR)/bin/tomo@$(TOMO_VERSION) | $(BUILD_DIR)/bin -- cgit v1.2.3 From 123e84696354be18bfd881b939d642b0a94f756f Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 21 Dec 2025 20:10:12 -0500 Subject: Fix slow manpage gzipping rules --- Makefile | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 5eba8e4e..7f247b49 100644 --- a/Makefile +++ b/Makefile @@ -131,11 +131,8 @@ BUILD_DIR=build/tomo@$(TOMO_VERSION) headers := $(wildcard src/stdlib/*.h) build_headers := $(patsubst src/stdlib/%.h, $(BUILD_DIR)/include/tomo@$(TOMO_VERSION)/%.h, $(headers)) -# find all man pages -manpages := $(wildcard man/*/*) - # generate corresponding build paths with .gz -build_manpages := $(foreach f,$(manpages),$(BUILD_DIR)/$(f).gz) +build_manpages := $(patsubst %,$(BUILD_DIR)/%.gz,$(wildcard man/man*/*)) # Ensure directories exist dirs := $(BUILD_DIR)/include/tomo@$(TOMO_VERSION) \ @@ -154,11 +151,7 @@ $(BUILD_DIR)/include/tomo@$(TOMO_VERSION)%.h: src/stdlib/%.h | $(BUILD_DIR)/incl cp $< $@ # Rule for gzipping man pages -$(BUILD_DIR)/man/man1/%.gz: man/man1/% | $(BUILD_DIR)/man/man1 - @$(ECHO) Gzipping manpage $< - gzip -c $< > $@ -$(BUILD_DIR)/man/man3/%.gz: man/man3/% | $(BUILD_DIR)/man/man3 - @$(ECHO) Gzipping manpage $< +$(BUILD_DIR)/man/%.gz: man/% | $(BUILD_DIR)/man/man1 $(BUILD_DIR)/man/man3 gzip -c $< > $@ $(BUILD_DIR)/bin/tomo: $(BUILD_DIR)/bin/tomo@$(TOMO_VERSION) | $(BUILD_DIR)/bin -- cgit v1.2.3 From 01cbec2d67399b7e8d18d409d1b1eccf00aba733 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 22 Dec 2025 14:23:04 -0500 Subject: Fix makefile progress indicator --- Makefile | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 7f247b49..965ccbe3 100644 --- a/Makefile +++ b/Makefile @@ -18,24 +18,14 @@ else include config.mk # Modified progress counter based on: https://stackoverflow.com/a/35320895 -# Only run counter if we're actually building (not for phony targets or no-ops) ifndef NO_PROGRESS ifndef ECHO -# Only count if building actual files, not just checking -ifneq ($(filter build all,$(MAKECMDGOALS)),) -T := $(shell $(MAKE) ECHO="COUNTTHIS" $(filter-out check-c-compiler check-libs,$(MAKECMDGOALS)) --no-print-directory \ - -nq 2>/dev/null | grep -c "COUNTTHIS") -ifeq ($(T),0) -ECHO = echo -else +T := $(shell $(MAKE) ECHO="COUNTTHIS" $(MAKECMDGOALS) --no-print-directory \ + -n | grep -c "COUNTTHIS") N := x C = $(words $N)$(eval N := x $N) ECHO = echo -e "[`expr $C '*' 100 / $T`%]" endif -else -ECHO = echo -endif -endif endif ifndef ECHO ECHO = echo -- cgit v1.2.3 From 0ee53cd5a79d41b124413d5da3e4279d06b17bfc Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 22 Dec 2025 14:34:42 -0500 Subject: Attempted improvements for building on mac --- .github/workflows/release.yml | 32 +++++++++++++++++++++++++++----- Makefile | 2 +- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1d6c5dba..2be7084e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,6 +5,10 @@ on: tags: - 'v*' +permissions: + contents: write + packages: write + jobs: linux-x86_64: runs-on: ubuntu-latest @@ -84,10 +88,31 @@ jobs: brew update brew install gmp libunistring bdw-gc llvm binutils - - name: Build + - name: Build arm64 run: | make clean - make -j + make -j \ + CC=clang \ + CFLAGS="-arch arm64" \ + LDFLAGS="-arch arm64" + mv build/tomo build/tomo-arm64 + + - name: Build x86_64 + run: | + make clean + make -j \ + CC=clang \ + CFLAGS="-arch x86_64" \ + LDFLAGS="-arch x86_64" + mv build/tomo build/tomo-x86_64 + + - name: Create universal binary + run: | + lipo -create \ + build/tomo-arm64 \ + build/tomo-x86_64 \ + -output build/tomo + lipo -info build/tomo - name: Package run: | @@ -102,6 +127,3 @@ jobs: tomo-macos-universal.tar.gz tomo-macos-universal.tar.gz.sha256 -permissions: - contents: write - packages: write diff --git a/Makefile b/Makefile index 965ccbe3..855c8b09 100644 --- a/Makefile +++ b/Makefile @@ -83,7 +83,7 @@ O=-O3 # Note: older versions of Make have buggy behavior with hash marks inside strings, so this ugly code is necessary: TOMO_VERSION=$(shell awk 'BEGIN{hashes=sprintf("%c%c",35,35)} $$1==hashes {print $$2; exit}' CHANGES.md) GIT_VERSION=$(shell git log -1 --pretty=format:"%as_%h" 2>/dev/null || echo "unknown") -CFLAGS=$(CCONFIG) $(INCLUDE_DIRS) $(EXTRA) $(CWARN) $(G) $(O) $(OSFLAGS) $(LTO) \ +CFLAGS += $(CCONFIG) $(INCLUDE_DIRS) $(EXTRA) $(CWARN) $(G) $(O) $(OSFLAGS) $(LTO) \ -DSUDO='"$(SUDO)"' -DDEFAULT_C_COMPILER='"$(DEFAULT_C_COMPILER)"' \ -DGIT_VERSION='"$(GIT_VERSION)"' CFLAGS_PLACEHOLDER="$$(printf '\033[2m\033[m\n')" -- cgit v1.2.3 From 83e6cc9197bd8e7a19834d291fe4c5e62639db38 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 22 Dec 2025 16:32:40 -0500 Subject: Add Path.writer() and Path.byte_writer() --- CHANGES.md | 4 ++ api/api.md | 52 +++++++++++++++++++++++++ api/paths.md | 52 +++++++++++++++++++++++++ api/paths.yaml | 76 ++++++++++++++++++++++++++++++++++++ man/man3/tomo-Path.3 | 18 ++++++++- man/man3/tomo-Path.byte_writer.3 | 42 ++++++++++++++++++++ man/man3/tomo-Path.writer.3 | 42 ++++++++++++++++++++ src/environment.c | 4 ++ src/stdlib/paths.c | 83 +++++++++++++++++++++++++++++++++++++--- src/stdlib/paths.h | 2 + 10 files changed, 368 insertions(+), 7 deletions(-) create mode 100644 man/man3/tomo-Path.byte_writer.3 create mode 100644 man/man3/tomo-Path.writer.3 diff --git a/CHANGES.md b/CHANGES.md index e609509b..72c4cef5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Version History +## v2025-12-22 + +- Added `Path.writer()` and `Path.byte_writer()` for multiple successive writes + ## v2025-12-21.6 - Add smarter default behavior if run without any args (REPL-like script runner) diff --git a/api/api.md b/api/api.md index 2be5e4e9..d035a3fe 100644 --- a/api/api.md +++ b/api/api.md @@ -2645,6 +2645,32 @@ else for line in (/dev/stdin).by_line()! say(line.upper()) +``` +## Path.byte_writer + +```tomo +Path.byte_writer : func(path: Path, append: Bool = no, permissions: Int32 = Int32(0o644) -> func(bytes:[Byte], close:Bool=no -> Result)) +``` + +Returns a function that can be used to repeatedly write bytes to the same file. + +The file writer will keep its file descriptor open after each write (unless the `close` argument is set to `yes`). If the file writer is never closed, it will be automatically closed when the file writer is garbage collected. + +Argument | Type | Description | Default +---------|------|-------------|--------- +path | `Path` | The path of the file to write to. | - +append | `Bool` | If set to `yes`, writes to the file will append. If set to `no`, then the first write to the file will overwrite its contents and subsequent calls will append. | `no` +permissions | `Int32` | The permissions to set on the file if it is created. | `Int32(0o644)` + +**Return:** Returns a function that can repeatedly write bytes to the same file. If `close` is set to `yes`, then the file will be closed after writing. If this function is called again after closing, the file will be reopened for appending. + + +**Example:** +```tomo +write := (./file.txt).byte_writer() +write("Hello\n".utf8())! +write("world\n".utf8(), close=yes)! + ``` ## Path.can_execute @@ -3463,6 +3489,32 @@ assert created == (./file-27QHtq.txt) assert created.read() == [1, 2, 3] created.remove() +``` +## Path.writer + +```tomo +Path.writer : func(path: Path, append: Bool = no, permissions: Int32 = Int32(0o644) -> func(text:Text, close:Bool=no -> Result)) +``` + +Returns a function that can be used to repeatedly write to the same file. + +The file writer will keep its file descriptor open after each write (unless the `close` argument is set to `yes`). If the file writer is never closed, it will be automatically closed when the file writer is garbage collected. + +Argument | Type | Description | Default +---------|------|-------------|--------- +path | `Path` | The path of the file to write to. | - +append | `Bool` | If set to `yes`, writes to the file will append. If set to `no`, then the first write to the file will overwrite its contents and subsequent calls will append. | `no` +permissions | `Int32` | The permissions to set on the file if it is created. | `Int32(0o644)` + +**Return:** Returns a function that can repeatedly write to the same file. If `close` is set to `yes`, then the file will be closed after writing. If this function is called again after closing, the file will be reopened for appending. + + +**Example:** +```tomo +write := (./file.txt).writer() +write("Hello\n")! +write("world\n", close=yes)! + ``` # Table diff --git a/api/paths.md b/api/paths.md index 435932e3..8c08b45b 100644 --- a/api/paths.md +++ b/api/paths.md @@ -117,6 +117,32 @@ else for line in (/dev/stdin).by_line()! say(line.upper()) +``` +## Path.byte_writer + +```tomo +Path.byte_writer : func(path: Path, append: Bool = no, permissions: Int32 = Int32(0o644) -> func(bytes:[Byte], close:Bool=no -> Result)) +``` + +Returns a function that can be used to repeatedly write bytes to the same file. + +The file writer will keep its file descriptor open after each write (unless the `close` argument is set to `yes`). If the file writer is never closed, it will be automatically closed when the file writer is garbage collected. + +Argument | Type | Description | Default +---------|------|-------------|--------- +path | `Path` | The path of the file to write to. | - +append | `Bool` | If set to `yes`, writes to the file will append. If set to `no`, then the first write to the file will overwrite its contents and subsequent calls will append. | `no` +permissions | `Int32` | The permissions to set on the file if it is created. | `Int32(0o644)` + +**Return:** Returns a function that can repeatedly write bytes to the same file. If `close` is set to `yes`, then the file will be closed after writing. If this function is called again after closing, the file will be reopened for appending. + + +**Example:** +```tomo +write := (./file.txt).byte_writer() +write("Hello\n".utf8())! +write("world\n".utf8(), close=yes)! + ``` ## Path.can_execute @@ -936,3 +962,29 @@ assert created.read() == [1, 2, 3] created.remove() ``` +## Path.writer + +```tomo +Path.writer : func(path: Path, append: Bool = no, permissions: Int32 = Int32(0o644) -> func(text:Text, close:Bool=no -> Result)) +``` + +Returns a function that can be used to repeatedly write to the same file. + +The file writer will keep its file descriptor open after each write (unless the `close` argument is set to `yes`). If the file writer is never closed, it will be automatically closed when the file writer is garbage collected. + +Argument | Type | Description | Default +---------|------|-------------|--------- +path | `Path` | The path of the file to write to. | - +append | `Bool` | If set to `yes`, writes to the file will append. If set to `no`, then the first write to the file will overwrite its contents and subsequent calls will append. | `no` +permissions | `Int32` | The permissions to set on the file if it is created. | `Int32(0o644)` + +**Return:** Returns a function that can repeatedly write to the same file. If `close` is set to `yes`, then the file will be closed after writing. If this function is called again after closing, the file will be reopened for appending. + + +**Example:** +```tomo +write := (./file.txt).writer() +write("Hello\n")! +write("world\n", close=yes)! + +``` diff --git a/api/paths.yaml b/api/paths.yaml index 02b8fbe8..a659ffbc 100644 --- a/api/paths.yaml +++ b/api/paths.yaml @@ -838,6 +838,82 @@ Path.write: example: | (./file.txt).write("Hello, world!") +Path.writer: + short: create a file writer + description: > + Returns a function that can be used to repeatedly write to the same file. + note: > + The file writer will keep its file descriptor open after each write (unless + the `close` argument is set to `yes`). If the file writer is never closed, + it will be automatically closed when the file writer is garbage collected. + return: + type: 'func(text:Text, close:Bool=no -> Result)' + description: > + Returns a function that can repeatedly write to the same file. If `close` + is set to `yes`, then the file will be closed after writing. If this + function is called again after closing, the file will be reopened for + appending. + args: + path: + type: 'Path' + description: > + The path of the file to write to. + append: + type: 'Bool' + default: 'no' + description: > + If set to `yes`, writes to the file will append. If set to `no`, then + the first write to the file will overwrite its contents and subsequent + calls will append. + permissions: + type: 'Int32' + default: 'Int32(0o644)' + description: > + The permissions to set on the file if it is created. + example: | + write := (./file.txt).writer() + write("Hello\n")! + write("world\n", close=yes)! + +Path.byte_writer: + short: create a byte-based file writer + description: > + Returns a function that can be used to repeatedly write bytes to the same + file. + note: > + The file writer will keep its file descriptor open after each write (unless + the `close` argument is set to `yes`). If the file writer is never closed, + it will be automatically closed when the file writer is garbage collected. + return: + type: 'func(bytes:[Byte], close:Bool=no -> Result)' + description: > + Returns a function that can repeatedly write bytes to the same file. If + `close` is set to `yes`, then the file will be closed after writing. If + this function is called again after closing, the file will be reopened + for appending. + args: + path: + type: 'Path' + description: > + The path of the file to write to. + append: + type: 'Bool' + default: 'no' + description: > + If set to `yes`, writes to the file will append. If set to `no`, then + the first write to the file will overwrite its contents and subsequent + calls will append. + permissions: + type: 'Int32' + default: 'Int32(0o644)' + description: > + The permissions to set on the file if it is created. + example: | + write := (./file.txt).byte_writer() + write("Hello\n".utf8())! + write("world\n".utf8(), close=yes)! + + Path.write_bytes: short: write bytes to a file description: > diff --git a/man/man3/tomo-Path.3 b/man/man3/tomo-Path.3 index ae9b6d51..b916005a 100644 --- a/man/man3/tomo-Path.3 +++ b/man/man3/tomo-Path.3 @@ -2,7 +2,7 @@ .\" Copyright (c) 2025 Bruce Hill .\" All rights reserved. .\" -.TH Path 3 2025-12-07 "Tomo man-pages" +.TH Path 3 2025-12-22 "Tomo man-pages" .SH NAME Path \- a Tomo type .SH LIBRARY @@ -50,6 +50,14 @@ For more, see: .BR Tomo-Path.by_line (3) +.TP +.BI Path.byte_writer\ :\ func(path:\ Path,\ append:\ Bool\ =\ no,\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ func(bytes:[Byte],\ close:Bool=no\ ->\ Result)) +Returns a function that can be used to repeatedly write bytes to the same file. + +For more, see: +.BR Tomo-Path.byte_writer (3) + + .TP .BI Path.can_execute\ :\ func(path:\ Path\ ->\ Bool) Returns whether or not a file can be executed by the current user/group. @@ -345,3 +353,11 @@ Writes the given bytes to a unique file path based on the specified path. The fi For more, see: .BR Tomo-Path.write_unique_bytes (3) + +.TP +.BI Path.writer\ :\ func(path:\ Path,\ append:\ Bool\ =\ no,\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ func(text:Text,\ close:Bool=no\ ->\ Result)) +Returns a function that can be used to repeatedly write to the same file. + +For more, see: +.BR Tomo-Path.writer (3) + diff --git a/man/man3/tomo-Path.byte_writer.3 b/man/man3/tomo-Path.byte_writer.3 new file mode 100644 index 00000000..595f0156 --- /dev/null +++ b/man/man3/tomo-Path.byte_writer.3 @@ -0,0 +1,42 @@ +'\" t +.\" Copyright (c) 2025 Bruce Hill +.\" All rights reserved. +.\" +.TH Path.byte_writer 3 2025-12-22 "Tomo man-pages" +.SH NAME +Path.byte_writer \- create a byte-based file writer +.SH LIBRARY +Tomo Standard Library +.SH SYNOPSIS +.nf +.BI Path.byte_writer\ :\ func(path:\ Path,\ append:\ Bool\ =\ no,\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ func(bytes:[Byte],\ close:Bool=no\ ->\ Result)) +.fi +.SH DESCRIPTION +Returns a function that can be used to repeatedly write bytes to the same file. + + +.SH ARGUMENTS + +.TS +allbox; +lb lb lbx lb +l l l l. +Name Type Description Default +path Path The path of the file to write to. - +append Bool If set to \fByes\fR, writes to the file will append. If set to \fBno\fR, then the first write to the file will overwrite its contents and subsequent calls will append. no +permissions Int32 The permissions to set on the file if it is created. Int32(0o644) +.TE +.SH RETURN +Returns a function that can repeatedly write bytes to the same file. If `close` is set to `yes`, then the file will be closed after writing. If this function is called again after closing, the file will be reopened for appending. + +.SH NOTES +The file writer will keep its file descriptor open after each write (unless the `close` argument is set to `yes`). If the file writer is never closed, it will be automatically closed when the file writer is garbage collected. + +.SH EXAMPLES +.EX +write := (./file.txt).byte_writer() +write("Hello\[rs]n".utf8())! +write("world\[rs]n".utf8(), close=yes)! +.EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/man/man3/tomo-Path.writer.3 b/man/man3/tomo-Path.writer.3 new file mode 100644 index 00000000..8b3d53d8 --- /dev/null +++ b/man/man3/tomo-Path.writer.3 @@ -0,0 +1,42 @@ +'\" t +.\" Copyright (c) 2025 Bruce Hill +.\" All rights reserved. +.\" +.TH Path.writer 3 2025-12-22 "Tomo man-pages" +.SH NAME +Path.writer \- create a file writer +.SH LIBRARY +Tomo Standard Library +.SH SYNOPSIS +.nf +.BI Path.writer\ :\ func(path:\ Path,\ append:\ Bool\ =\ no,\ permissions:\ Int32\ =\ Int32(0o644)\ ->\ func(text:Text,\ close:Bool=no\ ->\ Result)) +.fi +.SH DESCRIPTION +Returns a function that can be used to repeatedly write to the same file. + + +.SH ARGUMENTS + +.TS +allbox; +lb lb lbx lb +l l l l. +Name Type Description Default +path Path The path of the file to write to. - +append Bool If set to \fByes\fR, writes to the file will append. If set to \fBno\fR, then the first write to the file will overwrite its contents and subsequent calls will append. no +permissions Int32 The permissions to set on the file if it is created. Int32(0o644) +.TE +.SH RETURN +Returns a function that can repeatedly write to the same file. If `close` is set to `yes`, then the file will be closed after writing. If this function is called again after closing, the file will be reopened for appending. + +.SH NOTES +The file writer will keep its file descriptor open after each write (unless the `close` argument is set to `yes`). If the file writer is never closed, it will be automatically closed when the file writer is garbage collected. + +.SH EXAMPLES +.EX +write := (./file.txt).writer() +write("Hello\[rs]n")! +write("world\[rs]n", close=yes)! +.EE +.SH SEE ALSO +.BR Tomo-Path (3) diff --git a/src/environment.c b/src/environment.c index cf662749..a82274e7 100644 --- a/src/environment.c +++ b/src/environment.c @@ -341,6 +341,10 @@ env_t *global_env(bool source_mapping) { {"subdirectories", "Path$children", "func(path:Path, include_hidden=no -> [Path])"}, // {"unique_directory", "Path$unique_directory", "func(path:Path -> Path)"}, // {"write", "Path$write", "func(path:Path, text:Text, permissions=Int32(0o644) -> Result)"}, // + {"writer", "Path$writer", + "func(path:Path, append=no, permissions=Int32(0o644) -> func(text:Text, close=no -> Result))"}, // + {"byte_writer", "Path$byte_writer", + "func(path:Path, append=no, permissions=Int32(0o644) -> func(bytes:[Byte], close=no -> Result))"}, // {"write_bytes", "Path$write_bytes", "func(path:Path, bytes:[Byte], permissions=Int32(0o644) -> Result)"}, // {"write_unique", "Path$write_unique", "func(path:Path, text:Text -> Path?)"}, // {"write_unique_bytes", "Path$write_unique_bytes", "func(path:Path, bytes:[Byte] -> Path?)"}), diff --git a/src/stdlib/paths.c b/src/stdlib/paths.c index ed8383fd..9c74f4c1 100644 --- a/src/stdlib/paths.c +++ b/src/stdlib/paths.c @@ -324,6 +324,77 @@ Result_t Path$append_bytes(Path_t path, List_t bytes, int permissions) { return _write(path, bytes, O_WRONLY | O_APPEND | O_CREAT, permissions); } +typedef struct { + const char *path_str; + int fd; + int mode; + int permissions; +} writer_data_t; + +static Result_t _write_bytes_to_fd(List_t bytes, bool close_file, void *userdata) { + writer_data_t *data = userdata; + if (bytes.length > 0) { + int fd = open(data->path_str, data->mode, data->permissions); + if (fd == -1) { + if (errno == EMFILE || errno == ENFILE) { + // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that + // will be closed by GC finalizers. + GC_gcollect(); + fd = open(data->path_str, data->mode, data->permissions); + } + if (fd == -1) return FailureResult("Could not write to file: ", data->path_str, " (", strerror(errno), ")"); + } + data->fd = fd; + + if (bytes.stride != 1) List$compact(&bytes, 1); + ssize_t written = write(data->fd, bytes.data, (size_t)bytes.length); + if (written != (ssize_t)bytes.length) + return FailureResult("Could not write to file: ", data->path_str, " (", strerror(errno), ")"); + } + // After first successful write, all writes are appends + data->mode = (O_WRONLY | O_CREAT | O_APPEND); + + if (close_file && data->fd != -1) { + if (close(data->fd) == -1) + return FailureResult("Failed to close file: ", data->path_str, " (", strerror(errno), ")"); + data->fd = -1; + } + return SuccessResult; +} + +static Result_t _write_text_to_fd(Text_t text, bool close_file, void *userdata) { + return _write_bytes_to_fd(Text$utf8(text), close_file, userdata); +} + +static void _writer_cleanup(writer_data_t *data) { + if (data && data->fd != -1) { + close(data->fd); + data->fd = -1; + } +} + +public +Closure_t Path$byte_writer(Path_t path, bool append, int permissions) { + path = Path$expand_home(path); + const char *path_str = Path$as_c_string(path); + int mode = append ? (O_WRONLY | O_CREAT | O_APPEND) : (O_WRONLY | O_CREAT | O_TRUNC); + writer_data_t *userdata = + new (writer_data_t, .fd = -1, .path_str = path_str, .mode = mode, .permissions = permissions); + GC_register_finalizer(userdata, (void *)_writer_cleanup, NULL, NULL, NULL); + return (Closure_t){.fn = _write_bytes_to_fd, .userdata = userdata}; +} + +public +Closure_t Path$writer(Path_t path, bool append, int permissions) { + path = Path$expand_home(path); + const char *path_str = Path$as_c_string(path); + int mode = append ? (O_WRONLY | O_CREAT | O_APPEND) : (O_WRONLY | O_CREAT | O_TRUNC); + writer_data_t *userdata = + new (writer_data_t, .fd = -1, .path_str = path_str, .mode = mode, .permissions = permissions); + GC_register_finalizer(userdata, (void *)_writer_cleanup, NULL, NULL, NULL); + return (Closure_t){.fn = _write_text_to_fd, .userdata = userdata}; +} + public OptionalList_t Path$read_bytes(Path_t path, OptionalInt_t count) { path = Path$expand_home(path); @@ -331,8 +402,8 @@ OptionalList_t Path$read_bytes(Path_t path, OptionalInt_t count) { int fd = open(path_str, O_RDONLY); if (fd == -1) { if (errno == EMFILE || errno == ENFILE) { - // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that will - // be closed by GC finalizers. + // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that + // will be closed by GC finalizers. GC_gcollect(); fd = open(path_str, O_RDONLY); } @@ -705,8 +776,8 @@ OptionalClosure_t Path$by_line(Path_t path) { FILE *f = fopen(path_str, "r"); if (f == NULL) { if (errno == EMFILE || errno == ENFILE) { - // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that will - // be closed by GC finalizers. + // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that + // will be closed by GC finalizers. GC_gcollect(); f = fopen(path_str, "r"); } @@ -726,8 +797,8 @@ OptionalList_t Path$lines(Path_t path) { FILE *f = fopen(path_str, "r"); if (f == NULL) { if (errno == EMFILE || errno == ENFILE) { - // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that will - // be closed by GC finalizers. + // If we hit file handle limits, run GC collection to try to clean up any lingering file handles that + // will be closed by GC finalizers. GC_gcollect(); f = fopen(path_str, "r"); } diff --git a/src/stdlib/paths.h b/src/stdlib/paths.h index 881a3c78..c272314c 100644 --- a/src/stdlib/paths.h +++ b/src/stdlib/paths.h @@ -39,6 +39,8 @@ Result_t Path$write(Path_t path, Text_t text, int permissions); Result_t Path$write_bytes(Path_t path, List_t bytes, int permissions); Result_t Path$append(Path_t path, Text_t text, int permissions); Result_t Path$append_bytes(Path_t path, List_t bytes, int permissions); +Closure_t Path$byte_writer(Path_t path, bool append, int permissions); +Closure_t Path$writer(Path_t path, bool append, int permissions); OptionalText_t Path$read(Path_t path); OptionalList_t Path$read_bytes(Path_t path, OptionalInt_t limit); Result_t Path$set_owner(Path_t path, OptionalText_t owner, OptionalText_t group, bool follow_symlinks); -- cgit v1.2.3 From 04a8c524dbc4f13204b075dcb49b2f9b924a6f1c Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 22 Dec 2025 17:44:31 -0500 Subject: Use static linking to produce executables. --- CHANGES.md | 1 + Makefile | 2 +- src/tomo.c | 83 +++++++++++++++++++++++++++++++++++++++++++++----------------- 3 files changed, 62 insertions(+), 24 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 72c4cef5..976d7318 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,7 @@ ## v2025-12-22 +- Use static linking for compiled executables - Added `Path.writer()` and `Path.byte_writer()` for multiple successive writes ## v2025-12-21.6 diff --git a/Makefile b/Makefile index 855c8b09..bb807187 100644 --- a/Makefile +++ b/Makefile @@ -85,7 +85,7 @@ TOMO_VERSION=$(shell awk 'BEGIN{hashes=sprintf("%c%c",35,35)} $$1==hashes {print GIT_VERSION=$(shell git log -1 --pretty=format:"%as_%h" 2>/dev/null || echo "unknown") CFLAGS += $(CCONFIG) $(INCLUDE_DIRS) $(EXTRA) $(CWARN) $(G) $(O) $(OSFLAGS) $(LTO) \ -DSUDO='"$(SUDO)"' -DDEFAULT_C_COMPILER='"$(DEFAULT_C_COMPILER)"' \ - -DGIT_VERSION='"$(GIT_VERSION)"' + -DGIT_VERSION='"$(GIT_VERSION)"' -ffunction-sections -fdata-sections CFLAGS_PLACEHOLDER="$$(printf '\033[2m\033[m\n')" LDLIBS=-lgc -lm -lunistring -lgmp LIBTOMO_FLAGS=-shared diff --git a/src/tomo.c b/src/tomo.c index 5ebd6663..4b817e87 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -151,7 +151,6 @@ int main(int argc, char *argv[]) { if (stat(compiler_path, &compiler_stat) != 0) err(1, "Could not find age of compiler"); #endif - ldlibs = Texts(ldlibs, " -ltomo@", TOMO_VERSION); #ifdef __OpenBSD__ ldlibs = Texts(ldlibs, Text(" -lexecinfo")); #endif @@ -268,7 +267,8 @@ int main(int argc, char *argv[]) { cflags = Texts(cflags, Text(" -Wno-parentheses-equality")); } - ldflags = Texts("-Wl,-rpath,'", TOMO_PATH, "/lib' ", ldflags); + ldflags = + Texts("-Wl,-rpath,'", TOMO_PATH, "/lib' ", ldflags, " -ffunction-sections -fdata-sections -Wl,--gc-sections"); #ifdef __APPLE__ cflags = Texts(cflags, Text(" -I/opt/homebrew/include")); @@ -508,27 +508,38 @@ void build_library(Path_t lib_dir) { Text_t lib_name = get_library_name(lib_dir); Path_t shared_lib = Path$child(lib_dir, Texts(Text("lib"), lib_name, Text(SHARED_SUFFIX))); - if (!is_stale_for_any(shared_lib, object_files, false)) { - if (verbose) whisper("Unchanged: ", shared_lib); - return; - } - - FILE *prog = run_cmd(cc, " -O", optimization, " ", cflags, " ", ldflags, " ", ldlibs, " ", list_text(extra_ldlibs), + if (is_stale_for_any(shared_lib, object_files, false)) { + FILE *prog = + run_cmd(cc, " -O", optimization, " ", cflags, " ", ldflags, " ", ldlibs, " ", list_text(extra_ldlibs), #ifdef __APPLE__ - " -Wl,-install_name,@rpath/'lib", lib_name, SHARED_SUFFIX, - "'" + " -Wl,-install_name,@rpath/'lib", lib_name, SHARED_SUFFIX, + "'" #else - " -Wl,-soname,'lib", lib_name, SHARED_SUFFIX, - "'" + " -Wl,-soname,'lib", lib_name, SHARED_SUFFIX, + "'" #endif - " -shared ", - paths_str(object_files), " -o '", shared_lib, "'"); + " -shared ", + paths_str(object_files), " -o '", shared_lib, "'"); - if (!prog) print_err("Failed to run C compiler: ", cc); - int status = pclose(prog); - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) exit(EXIT_FAILURE); + if (!prog) print_err("Failed to run C compiler: ", cc); + int status = pclose(prog); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) exit(EXIT_FAILURE); + + if (!quiet) print("Compiled shared library:\t", Path$relative_to(shared_lib, Path$current_dir())); + } else { + if (verbose) whisper("Unchanged: ", shared_lib); + } - if (!quiet) print("Compiled library:\t", Path$relative_to(shared_lib, Path$current_dir())); + Path_t archive = Path$child(lib_dir, Texts(Text("lib"), lib_name, ".a")); + if (is_stale_for_any(archive, object_files, false)) { + FILE *prog = run_cmd("ar -rcs '", archive, "' ", paths_str(object_files)); + if (!prog) print_err("Failed to run `ar`"); + int status = pclose(prog); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) exit(EXIT_FAILURE); + if (!quiet) print("Compiled static library:\t", Path$relative_to(archive, Path$current_dir())); + } else { + if (verbose) whisper("Unchanged: ", archive); + } } void install_library(Path_t lib_dir) { @@ -715,9 +726,8 @@ void build_file_dependency_graph(Path_t path, Table_t *to_compile, Table_t *to_l case USE_MODULE: { module_info_t mod = get_used_module_info(stmt_ast); const char *full_name = mod.version ? String(mod.name, "@", mod.version) : mod.name; - Text_t lib = Texts("-Wl,-rpath,'", TOMO_PATH, "/lib/tomo@", TOMO_VERSION, "/", Text$from_str(full_name), - "' '", TOMO_PATH, "/lib/tomo@", TOMO_VERSION, "/", Text$from_str(full_name), "/lib", - Text$from_str(full_name), SHARED_SUFFIX "'"); + Text_t lib = Texts(TOMO_PATH, "/lib/tomo@", TOMO_VERSION, "/", Text$from_str(full_name), "/lib", + Text$from_str(full_name), ".a"); Table$set(to_link, &lib, NULL, Table$info(&Text$info, &Void$info)); List_t children = Path$glob( @@ -943,8 +953,35 @@ Path_t compile_executable(env_t *base_env, Path_t path, Path_t exe_path, List_t Path_t runner_file = build_file(path, ".runner.c"); Path$write(runner_file, program, 0644); - FILE *runner = run_cmd(cc, " ", cflags, " -O", optimization, " ", ldflags, " ", ldlibs, " ", - list_text(extra_ldlibs), " ", paths_str(object_files), " ", runner_file, " -o ", exe_path); + // .a archive files need to go later in the positional order: + List_t archives = EMPTY_LIST; + for (int64_t i = 0; i < (int64_t)extra_ldlibs.length;) { + Text_t *lib = (Text_t *)(extra_ldlibs.data + i * extra_ldlibs.stride); + if (Text$ends_with(*lib, Text(".a"), NULL)) { + List$insert(&archives, lib, I(0), sizeof(Text_t)); + List$remove_at(&extra_ldlibs, I(i + 1), I(1), sizeof(Text_t)); + } else { + i += 1; + } + } + + FILE *runner = run_cmd(cc, " ", + // C flags: + cflags, " -O", optimization, " ", + // Linker flags and dynamically linked shared libraries: + ldflags, " ", ldlibs, " ", list_text(extra_ldlibs), " ", + // Object files: + paths_str(object_files), " ", + // Input file: + runner_file, + // Statically linked archive files (must come after runner): + // Libraries are grouped to allow for circular dependencies among + // the libraries that are used. + " -Wl,--start-group ", list_text(archives), " -Wl,--end-group ", + // Tomo static library: + TOMO_PATH, "/lib/libtomo@", TOMO_VERSION, ".a", + // Output file: + " -o ", exe_path); if (show_codegen.length > 0) { FILE *out = run_cmd(show_codegen); -- cgit v1.2.3 From 7653cc84cff0b9168eeaccd056631e70a16930a0 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 22 Dec 2025 17:51:26 -0500 Subject: Rip out shared library code --- Makefile | 12 +----------- src/tomo.c | 33 ++------------------------------- 2 files changed, 3 insertions(+), 42 deletions(-) diff --git a/Makefile b/Makefile index bb807187..0356fa59 100644 --- a/Makefile +++ b/Makefile @@ -88,7 +88,6 @@ CFLAGS += $(CCONFIG) $(INCLUDE_DIRS) $(EXTRA) $(CWARN) $(G) $(O) $(OSFLAGS) $(LT -DGIT_VERSION='"$(GIT_VERSION)"' -ffunction-sections -fdata-sections CFLAGS_PLACEHOLDER="$$(printf '\033[2m\033[m\n')" LDLIBS=-lgc -lm -lunistring -lgmp -LIBTOMO_FLAGS=-shared ifeq ($(OS),OpenBSD) LDLIBS += -lexecinfo @@ -100,11 +99,6 @@ AR_FILE=libtomo@$(TOMO_VERSION).a ifeq ($(OS),Darwin) INCLUDE_DIRS += -I/opt/homebrew/include LDFLAGS += -L/opt/homebrew/lib - LIB_FILE=libtomo@$(TOMO_VERSION).dylib - LIBTOMO_FLAGS += -Wl,-install_name,@rpath/libtomo@$(TOMO_VERSION).dylib -else - LIB_FILE=libtomo@$(TOMO_VERSION).so - LIBTOMO_FLAGS += -Wl,-soname,libtomo@$(TOMO_VERSION).so endif EXE_FILE=tomo@$(TOMO_VERSION) @@ -151,10 +145,6 @@ $(BUILD_DIR)/bin/$(EXE_FILE): $(STDLIB_OBJS) $(COMPILER_OBJS) | $(BUILD_DIR)/bin @$(ECHO) $(CC) $(CFLAGS_PLACEHOLDER) $(LDFLAGS) $^ $(LDLIBS) -o $@ @$(CC) $(CFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@ -$(BUILD_DIR)/lib/$(LIB_FILE): $(STDLIB_OBJS) | $(BUILD_DIR)/lib - @$(ECHO) $(CC) $^ $(CFLAGS_PLACEHOLDER) $(OSFLAGS) $(LDFLAGS) $(LDLIBS) $(LIBTOMO_FLAGS) -o $@ - @$(CC) $^ $(CFLAGS) $(OSFLAGS) $(LDFLAGS) $(LDLIBS) $(LIBTOMO_FLAGS) -o $@ - $(BUILD_DIR)/lib/$(AR_FILE): $(STDLIB_OBJS) | $(BUILD_DIR)/lib ar -rcs $@ $^ @@ -164,7 +154,7 @@ $(BUILD_DIR)/lib/tomo@$(TOMO_VERSION)/modules.ini: modules/core.ini modules/exam $(BUILD_DIR)/share/licenses/tomo@$(TOMO_VERSION)/LICENSE.md: LICENSE.md | $(BUILD_DIR)/share/licenses/tomo@$(TOMO_VERSION) cp $< $@ -build: $(BUILD_DIR)/bin/tomo $(BUILD_DIR)/bin/tomo@$(TOMO_VERSION) $(BUILD_DIR)/lib/$(LIB_FILE) \ +build: $(BUILD_DIR)/bin/tomo $(BUILD_DIR)/bin/tomo@$(TOMO_VERSION) \ $(BUILD_DIR)/lib/$(AR_FILE) $(BUILD_DIR)/lib/tomo@$(TOMO_VERSION)/modules.ini \ $(BUILD_DIR)/share/licenses/tomo@$(TOMO_VERSION)/LICENSE.md $(build_headers) $(build_manpages) diff --git a/src/tomo.c b/src/tomo.c index 4b817e87..b8588566 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -69,12 +69,6 @@ static const char *paths_str(List_t paths) { return Text$as_c_string(result); } -#ifdef __APPLE__ -#define SHARED_SUFFIX ".dylib" -#else -#define SHARED_SUFFIX ".so" -#endif - static OptionalBool_t verbose = false, quiet = false, show_version = false, show_prefix = false, clean_build = false, source_mapping = true, should_install = false; @@ -507,29 +501,6 @@ void build_library(Path_t lib_dir) { compile_files(env, tm_files, &object_files, &extra_ldlibs, COMPILE_OBJ); Text_t lib_name = get_library_name(lib_dir); - Path_t shared_lib = Path$child(lib_dir, Texts(Text("lib"), lib_name, Text(SHARED_SUFFIX))); - if (is_stale_for_any(shared_lib, object_files, false)) { - FILE *prog = - run_cmd(cc, " -O", optimization, " ", cflags, " ", ldflags, " ", ldlibs, " ", list_text(extra_ldlibs), -#ifdef __APPLE__ - " -Wl,-install_name,@rpath/'lib", lib_name, SHARED_SUFFIX, - "'" -#else - " -Wl,-soname,'lib", lib_name, SHARED_SUFFIX, - "'" -#endif - " -shared ", - paths_str(object_files), " -o '", shared_lib, "'"); - - if (!prog) print_err("Failed to run C compiler: ", cc); - int status = pclose(prog); - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) exit(EXIT_FAILURE); - - if (!quiet) print("Compiled shared library:\t", Path$relative_to(shared_lib, Path$current_dir())); - } else { - if (verbose) whisper("Unchanged: ", shared_lib); - } - Path_t archive = Path$child(lib_dir, Texts(Text("lib"), lib_name, ".a")); if (is_stale_for_any(archive, object_files, false)) { FILE *prog = run_cmd("ar -rcs '", archive, "' ", paths_str(object_files)); @@ -556,11 +527,11 @@ void install_library(Path_t lib_dir) { } // If we have `debugedit` on this system, use it to remap the debugging source information // to point to the installed version of the source file. Otherwise, fail silently. - if (verbose) whisper("Updating debug symbols for ", dest, "/lib", lib_name, SHARED_SUFFIX); + if (verbose) whisper("Updating debug symbols for ", dest, "/lib", lib_name, ".a"); int result = system(String(as_owner, "debugedit -b ", lib_dir, " -d '", dest, "'" " '", - dest, "/lib", lib_name, SHARED_SUFFIX, + dest, "/lib", lib_name, ".a", "' " ">/dev/null 2>/dev/null")); (void)result; -- cgit v1.2.3 From cd6923bf137caa24c3cc2d05965c7cfa2428fb4d Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 22 Dec 2025 17:53:02 -0500 Subject: Update changelog --- CHANGES.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 976d7318..9690a745 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,9 @@ ## v2025-12-22 -- Use static linking for compiled executables +- Use static linking instead of dynamic linking for the Tomo standard library + as well as for user libraries. This produces binaries that do not depend on + having Tomo and the library installed at runtime. - Added `Path.writer()` and `Path.byte_writer()` for multiple successive writes ## v2025-12-21.6 -- cgit v1.2.3 From 32cbf32c91688b93c40e457e61b29decb416a6e7 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 22 Dec 2025 18:02:47 -0500 Subject: Clean up .git/ when installing libraries --- src/modules.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modules.c b/src/modules.c index 056cd5cc..c7c29d24 100644 --- a/src/modules.c +++ b/src/modules.c @@ -131,6 +131,9 @@ bool try_install_module(module_info_t mod, bool ask_confirmation) { if (mod.revision) xsystem("git clone --depth=1 --revision ", mod.revision, " ", mod.git, " ", dest); else if (mod.version) xsystem("git clone --depth=1 --branch ", mod.version, " ", mod.git, " ", dest); else xsystem("git clone --depth=1 ", mod.git, " ", dest); + // Clean up .git/ folder after cloning: + xsystem("rm -rf ", dest, "/.git"); + // Build library: xsystem("tomo -L ", dest); return true; } else if (mod.url) { -- cgit v1.2.3 From 903c957c7e686401e12bf52ad0ac00d0efa6fd30 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 22 Dec 2025 18:07:12 -0500 Subject: Bugfix for `Success || Void` typechecking --- src/typecheck.c | 6 +----- src/types.c | 9 +++++++++ src/types.h | 1 + 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/typecheck.c b/src/typecheck.c index 1ac3943c..a073cb2e 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -1531,11 +1531,7 @@ PUREFUNC bool is_discardable(env_t *env, ast_t *ast) { case Metadata: return true; default: break; } - type_t *t = get_type(env, ast); - if (t->tag == StructType) { - return (Match(t, StructType)->fields == NULL); - } - return (t->tag == VoidType || t->tag == AbortType || t->tag == ReturnType); + return is_discardable_type(get_type(env, ast)); } type_t *get_arg_ast_type(env_t *env, arg_ast_t *arg) { diff --git a/src/types.c b/src/types.c index 46df5c64..1ccb7952 100644 --- a/src/types.c +++ b/src/types.c @@ -144,6 +144,13 @@ PUREFUNC type_t *value_type(type_t *t) { return t; } +PUREFUNC bool is_discardable_type(type_t *t) { + if (t->tag == StructType) { + return (Match(t, StructType)->fields == NULL); + } + return (t->tag == VoidType || t->tag == AbortType || t->tag == ReturnType); +} + type_t *type_or_type(type_t *a, type_t *b) { if (!a) return b; if (!b) return a; @@ -153,6 +160,8 @@ type_t *type_or_type(type_t *a, type_t *b) { return a->tag == OptionalType ? a : Type(OptionalType, a); if (a->tag == ReturnType && b->tag == ReturnType) return Type(ReturnType, .ret = type_or_type(Match(a, ReturnType)->ret, Match(b, ReturnType)->ret)); + if ((a->tag == VoidType && is_discardable_type(b)) || (is_discardable_type(a) && b->tag == VoidType)) + return Type(VoidType); if (is_incomplete_type(a) && type_eq(b, most_complete_type(a, b))) return b; if (is_incomplete_type(b) && type_eq(a, most_complete_type(a, b))) return a; diff --git a/src/types.h b/src/types.h index 66e6ba12..df5729ca 100644 --- a/src/types.h +++ b/src/types.h @@ -142,6 +142,7 @@ PUREFUNC bool type_eq(type_t *a, type_t *b); PUREFUNC bool type_is_a(type_t *t, type_t *req); type_t *type_or_type(type_t *a, type_t *b); type_t *value_type(type_t *a); +PUREFUNC bool is_discardable_type(type_t *t); typedef enum { NUM_PRECISION_EQUAL, NUM_PRECISION_LESS, -- cgit v1.2.3 From 354605f56937e6f51695084d208521f7f2df890c Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 22 Dec 2025 18:17:03 -0500 Subject: Fix for CLI usage showing flag type instead of flag name for positional args --- src/compile/cli.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/compile/cli.c b/src/compile/cli.c index 63a467ca..ade6caa7 100644 --- a/src/compile/cli.c +++ b/src/compile/cli.c @@ -83,10 +83,8 @@ static Text_t generate_usage(env_t *env, type_t *fn_type) { else if (t->tag == ListType) usage = Texts(usage, "[", flags, " ", get_flag_options(t, Text("|")), "]"); else if (t->tag == EnumType) usage = Texts(usage, "[", flags, " val]"); else usage = Texts(usage, "[", flags, " ", get_flag_options(t, Text("|")), "]"); - } else if (t->tag == EnumType) { - usage = Texts(usage, "\x1b[1m", flag, "\x1b[m"); } else { - usage = Texts(usage, "\x1b[1m", get_flag_options(t, Text("|")), "\x1b[m"); + usage = Texts(usage, "\x1b[1m", flag, "\x1b[m"); } } return usage; -- cgit v1.2.3 From f10273d2ef7d5bdd3592c4c19476bd62a9b4a164 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 22 Dec 2025 18:32:31 -0500 Subject: Allow writes without reads to global vars --- src/compile/functions.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/compile/functions.c b/src/compile/functions.c index ec37c0ad..01377a89 100644 --- a/src/compile/functions.c +++ b/src/compile/functions.c @@ -630,6 +630,8 @@ static void check_unused_vars(env_t *env, arg_ast_t *args, ast_t *body) { const char *name; } *entry = unused.entries.data + i * unused.entries.stride; if (streq(entry->name, "_")) continue; + // Global/file scoped vars are okay to mutate without reading + if (get_binding(env, entry->name) != NULL) continue; ast_t *var = Table$str_get(assigned_vars, entry->name); code_err(var, "This variable was assigned to, but never read from."); } -- cgit v1.2.3 From 8b255d8f23c121fbec478a714e580b32641853d7 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 22 Dec 2025 19:48:56 -0500 Subject: Update learnxiny code --- examples/learnxiny.tm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/learnxiny.tm b/examples/learnxiny.tm index 748f8957..6971f2a9 100644 --- a/examples/learnxiny.tm +++ b/examples/learnxiny.tm @@ -19,7 +19,7 @@ func main() my_num := 2.0 # Strings can use interpolation with the dollar sign $: - say("My variable is $my_variable and this is a sum: $(1 + 2)") + say("My variable is $my_variable, my num is $my_num, and this is a sum: $(1 + 2)") say(" Multiline strings begin with a " at the end of a line and continue in @@ -115,6 +115,7 @@ func main() # Empty tables require specifying the key and value types: empty_table : {Text:Int} + assert empty_table == {} # Tables can be iterated over either by key or key,value: for key in table -- cgit v1.2.3 From a89500afd9a34f2af94ab6fcd7a0469b0450732a Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 22 Dec 2025 19:52:24 -0500 Subject: Update example versions --- modules/examples.ini | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/examples.ini b/modules/examples.ini index 20a04639..8e53d431 100644 --- a/modules/examples.ini +++ b/modules/examples.ini @@ -1,27 +1,27 @@ [log] -version=v1.0 +version=v2025-12-22 git=https://github.com/bruce-hill/tomo-log [ini] -version=v1.0 +version=v2025-12-22 git=https://github.com/bruce-hill/tomo-ini [vectors] -version=v1.0 +version=v1.1 git=https://github.com/bruce-hill/tomo-vectors [http] -version=v1.1 +version=v1.2 git=https://github.com/bruce-hill/tomo-http [http-server] -version=v1.0 +version=v2025-12-22 git=https://github.com/bruce-hill/tomo-http-server [wrap] -version=v1.0 +version=v2025-12-22 git=https://github.com/bruce-hill/tomo-wrap [colorful] -version=v1.0 +version=v2025-11-29 git=https://github.com/bruce-hill/tomo-colorful -- cgit v1.2.3 From 4a6c0438f9a2c82e834116e3e1bc110b8cae8432 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 23 Dec 2025 13:58:33 -0500 Subject: Big speedup my trimming down MAP_LIST macro and inlining some applications of it. --- CHANGES.md | 4 ++++ src/compile/assertions.c | 14 +++++--------- src/compile/indexing.c | 2 +- src/compile/optionals.c | 8 ++++---- src/compile/promotions.c | 12 ++++++------ src/compile/text.c | 15 +++------------ src/stdlib/bytes.c | 2 +- src/stdlib/c_strings.c | 4 ++-- src/stdlib/enums.c | 2 +- src/stdlib/intX.c.h | 2 +- src/stdlib/lists.h | 10 ++++++---- src/stdlib/mapmacro.h | 4 +--- src/stdlib/memory.c | 2 +- src/stdlib/numX.c.h | 6 +++--- src/stdlib/paths.c | 4 ++-- src/stdlib/pointers.c | 8 ++++---- src/stdlib/stdlib.h | 4 ++-- src/stdlib/structs.c | 4 ++-- src/stdlib/tables.c | 6 +++--- src/stdlib/tables.h | 4 ++-- src/stdlib/text.c | 16 ++++++++-------- src/stdlib/text.h | 5 +++-- 22 files changed, 65 insertions(+), 73 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9690a745..6f8f5e16 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Version History +## v2025-12-23 + +- Improved C preprocessing performance by eliminating expensive macro calls. + ## v2025-12-22 - Use static linking instead of dynamic linking for the Tomo standard library diff --git a/src/compile/assertions.c b/src/compile/assertions.c index 34055998..18531fd9 100644 --- a/src/compile/assertions.c +++ b/src/compile/assertions.c @@ -60,13 +60,10 @@ Text_t compile_assertion(env_t *env, ast_t *ast) { compile_declaration(operand_t, Text("_rhs")), " = ", compile_to_type(env, cmp.rhs, operand_t), ";\n", "\n#line ", line, "\n", "if (!(", compile_condition(env, var_comparison), "))\n", "#line ", line, "\n", Texts("fail_source(", quoted_str(ast->file->filename), ", ", (int64_t)(expr->start - expr->file->text), - ", ", (int64_t)(expr->end - expr->file->text), ", ", - message ? Texts("Text$as_c_string(", compile_to_type(env, message, Type(TextType)), ")") - : Text("\"This assertion failed!\""), - ", ", "\" (\", ", expr_as_text(Text("_lhs"), operand_t, Text("no")), - ", " - "\" ", - failure, " \", ", expr_as_text(Text("_rhs"), operand_t, Text("no")), ", \")\");\n"), + ", ", (int64_t)(expr->end - expr->file->text), ", Text$concat(", + message ? compile_to_type(env, message, Type(TextType)) : Text("Text(\"This assertion failed!\")"), + ", Text(\" (\"), ", expr_as_text(Text("_lhs"), operand_t, Text("no")), ", Text(\" ", failure, + " \"), ", expr_as_text(Text("_rhs"), operand_t, Text("no")), ", Text(\")\")));\n"), "}\n"); } default: { @@ -74,8 +71,7 @@ Text_t compile_assertion(env_t *env, ast_t *ast) { return Texts("if (!(", compile_condition(env, expr), "))\n", "#line ", line, "\n", "fail_source(", quoted_str(ast->file->filename), ", ", (int64_t)(expr->start - expr->file->text), ", ", (int64_t)(expr->end - expr->file->text), ", ", - message ? Texts("Text$as_c_string(", compile_to_type(env, message, Type(TextType)), ")") - : Text("\"This assertion failed!\""), + message ? compile_to_type(env, message, Type(TextType)) : Text("Text(\"This assertion failed!\")"), ");\n"); } } diff --git a/src/compile/indexing.c b/src/compile/indexing.c index 13062641..031ef9a0 100644 --- a/src/compile/indexing.c +++ b/src/compile/indexing.c @@ -50,7 +50,7 @@ Text_t compile_indexing(env_t *env, ast_t *ast, bool checked) { return Texts("({ ", compile_declaration(item_type, Text("opt")), " = ", code, "; ", "if unlikely (", check_none(item_type, Text("opt")), ")\n", "#line ", line, "\n", "fail_source(", quoted_str(ast->file->filename), ", ", start, ", ", end, ", ", - "\"This was expected to be a value, but it's `none`\\n\");\n", + "Text(\"This was expected to be a value, but it's `none`\\n\"));\n", optional_into_nonnone(item_type, Text("opt")), "; })"); } return code; diff --git a/src/compile/optionals.c b/src/compile/optionals.c index 83f387e2..75dff935 100644 --- a/src/compile/optionals.c +++ b/src/compile/optionals.c @@ -134,9 +134,9 @@ Text_t compile_non_optional(env_t *env, ast_t *ast) { compile_to_pointer_depth(env, f->fielded, 0, true), ";", "if unlikely (_test_enum.$tag != ", tag_name, ") {\n", "#line ", line, "\n", "fail_source(", quoted_str(f->fielded->file->filename), ", ", (int64_t)(f->fielded->start - f->fielded->file->text), - ", ", (int64_t)(f->fielded->end - f->fielded->file->text), ", ", "\"This was expected to be ", - tag->name, ", but it was: \", ", expr_as_text(Text("_test_enum"), enum_t, Text("false")), - ", \"\\n\");\n}\n", + ", ", (int64_t)(f->fielded->end - f->fielded->file->text), ", ", + "Text$concat(Text(\"This was expected to be ", tag->name, ", but it was: \"), ", + expr_as_text(Text("_test_enum"), enum_t, Text("false")), ", Text(\"\\n\")));\n}\n", compile_maybe_incref( env, WrapLiteralCode(value, Texts("_test_enum.", tag->name), .type = tag->type), tag->type), "; })"); @@ -149,7 +149,7 @@ Text_t compile_non_optional(env_t *env, ast_t *ast) { check_none(value_t, Text("opt")), ")\n", "#line ", line, "\n", "fail_source(", quoted_str(value->file->filename), ", ", (int64_t)(value->start - value->file->text), ", ", (int64_t)(value->end - value->file->text), ", ", - "\"This was expected to be a value, but it's `none`\\n\");\n", + "Text(\"This was expected to be a value, but it's `none`\\n\"));\n", optional_into_nonnone(value_t, Text("opt")), "; })"); } } diff --git a/src/compile/promotions.c b/src/compile/promotions.c index 5b0ccb95..482e5ed0 100644 --- a/src/compile/promotions.c +++ b/src/compile/promotions.c @@ -61,12 +61,12 @@ bool promote(env_t *env, ast_t *ast, Text_t *code, type_t *actual, type_t *neede // Automatic optional checking for nums: if (needed->tag == NumType && actual->tag == OptionalType && Match(actual, OptionalType)->type->tag == NumType) { int64_t line = get_line_number(ast->file, ast->start); - *code = - Texts("({ ", compile_declaration(actual, Text("opt")), " = ", *code, "; ", "if unlikely (", - check_none(actual, Text("opt")), ")\n", "#line ", line, "\n", "fail_source(", - quoted_str(ast->file->filename), ", ", (int64_t)(ast->start - ast->file->text), ", ", - (int64_t)(ast->end - ast->file->text), ", ", "\"This was expected to be a value, but it's none\");\n", - optional_into_nonnone(actual, Text("opt")), "; })"); + *code = Texts("({ ", compile_declaration(actual, Text("opt")), " = ", *code, "; ", "if unlikely (", + check_none(actual, Text("opt")), ")\n", "#line ", line, "\n", "fail_source(", + quoted_str(ast->file->filename), ", ", (int64_t)(ast->start - ast->file->text), ", ", + (int64_t)(ast->end - ast->file->text), ", ", + "Text(\"This was expected to be a value, but it's none\"));\n", + optional_into_nonnone(actual, Text("opt")), "; })"); return true; } diff --git a/src/compile/text.c b/src/compile/text.c index 3a8a227c..2a64ca45 100644 --- a/src/compile/text.c +++ b/src/compile/text.c @@ -4,7 +4,6 @@ #include "../ast.h" #include "../environment.h" -#include "../naming.h" #include "../stdlib/datatypes.h" #include "../stdlib/tables.h" #include "../stdlib/text.h" @@ -102,19 +101,12 @@ Text_t compile_text_ast(env_t *env, ast_t *ast) { type_t *text_t = lang ? Table$str_get(*env->types, lang) : TEXT_TYPE; if (!text_t || text_t->tag != TextType) code_err(ast, quoted(lang), " is not a valid text language name"); - Text_t lang_constructor; - if (!lang || streq(lang, "Text")) lang_constructor = Text("Text"); - else - lang_constructor = namespace_name(Match(text_t, TextType)->env, Match(text_t, TextType)->env->namespace->parent, - Text$from_str(lang)); - ast_list_t *chunks = Match(ast, TextJoin)->children; if (!chunks) { - return Texts(lang_constructor, "(\"\")"); + return Text("EMPTY_TEXT"); } else if (!chunks->next && chunks->ast->tag == TextLiteral) { Text_t literal = Match(chunks->ast, TextLiteral)->text; - if (string_literal_is_all_ascii(literal)) - return Texts(lang_constructor, "(", compile_text_literal(literal), ")"); + if (string_literal_is_all_ascii(literal)) return Texts("Text(", compile_text_literal(literal), ")"); return Texts("((", compile_type(text_t), ")", compile(env, chunks->ast), ")"); } else { Text_t code = EMPTY_TEXT; @@ -142,7 +134,6 @@ Text_t compile_text_ast(env_t *env, ast_t *ast) { code = Texts(code, chunk_code); if (chunk->next) code = Texts(code, ", "); } - if (chunks->next) return Texts(lang_constructor, "s(", code, ")"); - else return code; + return Texts("Text$concat(", code, ")"); } } diff --git a/src/stdlib/bytes.c b/src/stdlib/bytes.c index 4416d804..a1e953a0 100644 --- a/src/stdlib/bytes.c +++ b/src/stdlib/bytes.c @@ -25,7 +25,7 @@ PUREFUNC public Text_t Byte$as_text(const void *b, bool colorize, const TypeInfo '\0', }; Text_t text = Text$from_str(digits); - if (colorize) text = Texts(Text("\x1b[35m"), text, Text("\x1b[m")); + if (colorize) text = Text$concat(Text("\x1b[35m"), text, Text("\x1b[m")); return text; } diff --git a/src/stdlib/c_strings.c b/src/stdlib/c_strings.c index 57960577..cbe46b68 100644 --- a/src/stdlib/c_strings.c +++ b/src/stdlib/c_strings.c @@ -70,8 +70,8 @@ const char *CString$join(const char *glue, List_t strings) { Text_t ret = EMPTY_TEXT; Text_t glue_text = Text$from_str(glue); for (int64_t i = 0; i < (int64_t)strings.length; i++) { - if (i > 0) ret = Texts(ret, glue_text); - ret = Texts(ret, Text$from_str(*(const char **)(strings.data + i * strings.stride))); + if (i > 0) ret = Text$concat(ret, glue_text); + ret = Text$concat(ret, Text$from_str(*(const char **)(strings.data + i * strings.stride))); } return Text$as_c_string(ret); } diff --git a/src/stdlib/enums.c b/src/stdlib/enums.c index b9b970fa..9cc16c5d 100644 --- a/src/stdlib/enums.c +++ b/src/stdlib/enums.c @@ -65,7 +65,7 @@ Text_t Enum$as_text(const void *obj, bool colorize, const TypeInfo_t *type) { NamedType_t value = type->EnumInfo.tags[tag - 1]; if (!value.type || value.type->size == 0) { Text_t text = Text$from_str(value.name); - return colorize ? Texts(Text("\x1b[1m"), text, Text("\x1b[m")) : text; + return colorize ? Text$concat(Text("\x1b[1m"), text, Text("\x1b[m")) : text; } return generic_as_text(obj + value_offset(type), colorize, value.type); diff --git a/src/stdlib/intX.c.h b/src/stdlib/intX.c.h index 72dfe6ed..03322e5b 100644 --- a/src/stdlib/intX.c.h +++ b/src/stdlib/intX.c.h @@ -98,7 +98,7 @@ Text_t NAMESPACED(as_text)(const void *i, bool colorize, const TypeInfo_t *info) (void)info; if (!i) return Text(NAME_STR); Text_t text = _int64_to_text((int64_t)(*(INT_T *)i)); - return colorize ? Texts(Text("\033[35m"), text, Text("\033[m")) : text; + return colorize ? Text$concat(Text("\033[35m"), text, Text("\033[m")) : text; } public Text_t NAMESPACED(value_as_text)(INT_T i) { return _int64_to_text((int64_t)i); } diff --git a/src/stdlib/lists.h b/src/stdlib/lists.h index 6a0a1d43..9ac8bf1b 100644 --- a/src/stdlib/lists.h +++ b/src/stdlib/lists.h @@ -20,8 +20,9 @@ extern char _EMPTY_LIST_SENTINEL; int64_t index = index_expr; \ int64_t off = index + (index < 0) * (list.length + 1) - 1; \ if (unlikely(off < 0 || off >= list.length)) \ - fail_source(__SOURCE_FILE__, start, end, "Invalid list index: ", index, " (list has length ", \ - (int64_t)list.length, ")\n"); \ + fail_source(__SOURCE_FILE__, start, end, \ + Text$concat(Text("Invalid list index: "), convert_to_text(index), Text(" (list has length "), \ + convert_to_text((int64_t)list.length), Text(")\n"))); \ *(item_type *)(list.data + list.stride * off); \ }) #define List_get(list_expr, index_expr, item_type, var, optional_expr, none_expr) \ @@ -40,8 +41,9 @@ extern char _EMPTY_LIST_SENTINEL; int64_t index = index_expr; \ int64_t off = index + (index < 0) * (list->length + 1) - 1; \ if (unlikely(off < 0 || off >= list->length)) \ - fail_source(__SOURCE_FILE__, start, end, "Invalid list index: ", index, " (list has length ", \ - (int64_t)list->length, ")\n"); \ + fail_source(__SOURCE_FILE__, start, end, \ + Text$concat(Text("Invalid list index: "), convert_to_text(index), Text(" (list has length "), \ + convert_to_text((int64_t)list->length), Text(")\n"))); \ if (list->data_refcount > 0) List$compact(list, sizeof(item_type)); \ (item_type *)(list->data + list->stride * off); \ }) diff --git a/src/stdlib/mapmacro.h b/src/stdlib/mapmacro.h index 7b0e3c4e..5e9eaa36 100644 --- a/src/stdlib/mapmacro.h +++ b/src/stdlib/mapmacro.h @@ -7,9 +7,7 @@ #define EVAL0(...) __VA_ARGS__ #define EVAL1(...) EVAL0(EVAL0(EVAL0(__VA_ARGS__))) #define EVAL2(...) EVAL1(EVAL1(EVAL1(__VA_ARGS__))) -#define EVAL3(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__))) -#define EVAL4(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__))) -#define EVAL(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__))) +#define EVAL(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__))) #define MAP_END(...) #define MAP_OUT diff --git a/src/stdlib/memory.c b/src/stdlib/memory.c index fd396463..53a180fb 100644 --- a/src/stdlib/memory.c +++ b/src/stdlib/memory.c @@ -18,7 +18,7 @@ Text_t Memory$as_text(const void *p, bool colorize, const TypeInfo_t *info) { (void)info; if (!p) return Text("Memory"); Text_t text = Text$from_str(String("Memory<", (void *)p, ">")); - return colorize ? Texts(Text("\x1b[0;34;1m"), text, Text("\x1b[m")) : text; + return colorize ? Text$concat(Text("\x1b[0;34;1m"), text, Text("\x1b[m")) : text; } public diff --git a/src/stdlib/numX.c.h b/src/stdlib/numX.c.h index 0e84708f..2fde8c45 100644 --- a/src/stdlib/numX.c.h +++ b/src/stdlib/numX.c.h @@ -50,7 +50,7 @@ PUREFUNC Text_t NAMESPACED(as_text)(const void *x, bool colorize, const TypeInfo if (!x) return Text(TYPE_STR); static const Text_t color_prefix = Text("\x1b[35m"), color_suffix = Text("\x1b[m"); Text_t text = NAMESPACED(value_as_text)(*(NUM_T *)x); - return colorize ? Texts(color_prefix, text, color_suffix) : text; + return colorize ? Text$concat(color_prefix, text, color_suffix) : text; } public PUREFUNC int32_t NAMESPACED(compare)(const void *x, const void *y, const TypeInfo_t *info) { @@ -73,7 +73,7 @@ PUREFUNC Text_t NAMESPACED(as_text)(const void *x, bool colorize, const TypeInfo if (!x) return Text(TYPE_STR); static const Text_t color_prefix = Text("\x1b[35m"), color_suffix = Text("\x1b[m"); Text_t text = Num$value_as_text((double)*(NUM_T *)x); - return colorize ? Texts(color_prefix, text, color_suffix) : text; + return colorize ? Text$concat(color_prefix, text, color_suffix) : text; } public PUREFUNC int32_t NAMESPACED(compare)(const void *x, const void *y, const TypeInfo_t *info) { @@ -115,7 +115,7 @@ public Text_t NAMESPACED(percent)(NUM_T x, NUM_T precision) { NUM_T d = SUFFIXED(100.) * x; d = NAMESPACED(with_precision)(d, precision); - return Texts(NAMESPACED(value_as_text)(d), Text("%")); + return Text$concat(NAMESPACED(value_as_text)(d), Text("%")); } public diff --git a/src/stdlib/paths.c b/src/stdlib/paths.c index 9c74f4c1..e3028cce 100644 --- a/src/stdlib/paths.c +++ b/src/stdlib/paths.c @@ -690,7 +690,7 @@ bool Path$has_extension(Path_t path, Text_t extension) { if (extension.length == 0) return !Text$has(Text$from(last, I(2)), Text(".")) || Text$equal_values(last, Text("..")); - if (!Text$starts_with(extension, Text("."), NULL)) extension = Texts(Text("."), extension); + if (!Text$starts_with(extension, Text("."), NULL)) extension = Text$concat(Text("."), extension); return Text$ends_with(Text$from(last, I(2)), extension, NULL); } @@ -879,7 +879,7 @@ Text_t Path$as_text(const void *obj, bool color, const TypeInfo_t *type) { && (path->components.length == 0 || !Text$equal_values(*(Text_t *)(path->components.data), Text("..")))) text = Text$concat(path->components.length > 0 ? Text("./") : Text("."), text); - if (color) text = Texts(Text("\033[32;1m"), text, Text("\033[m")); + if (color) text = Text$concat(Text("\033[32;1m"), text, Text("\033[m")); return text; } diff --git a/src/stdlib/pointers.c b/src/stdlib/pointers.c index 74037613..0bf9a274 100644 --- a/src/stdlib/pointers.c +++ b/src/stdlib/pointers.c @@ -38,14 +38,14 @@ Text_t Pointer$as_text(const void *x, bool colorize, const TypeInfo_t *type) { if (top_level) { root = ptr; } else if (ptr == root) { - Text_t text = Texts(Text$from_str(ptr_info.sigil), Text("~1")); - return colorize ? Texts(Text("\x1b[34;1m"), text, Text("\x1b[m")) : text; + Text_t text = Text$concat(Text$from_str(ptr_info.sigil), Text("~1")); + return colorize ? Text$concat(Text("\x1b[34;1m"), text, Text("\x1b[m")) : text; } else { TypeInfo_t rec_table = *Table$info(type, &Int64$info); int64_t *id = Table$get(pending, x, &rec_table); if (id) { - Text_t text = Texts(Text$from_str(ptr_info.sigil), Int64$value_as_text(*id)); - return colorize ? Texts(Text("\x1b[34;1m"), text, Text("\x1b[m")) : text; + Text_t text = Text$concat(Text$from_str(ptr_info.sigil), Int64$value_as_text(*id)); + return colorize ? Text$concat(Text("\x1b[34;1m"), text, Text("\x1b[m")) : text; } int64_t next_id = (int64_t)pending.entries.length + 2; Table$set(&pending, x, &next_id, &rec_table); diff --git a/src/stdlib/stdlib.h b/src/stdlib/stdlib.h index 234048e9..087ed4bf 100644 --- a/src/stdlib/stdlib.h +++ b/src/stdlib/stdlib.h @@ -37,7 +37,7 @@ void tomo_cleanup(void); exit(1); \ }) -#define fail_source(filename, start, end, ...) \ +#define fail_source(filename, start, end, message) \ ({ \ tomo_cleanup(); \ fflush(stdout); \ @@ -46,7 +46,7 @@ void tomo_cleanup(void); print_stacktrace(stderr, 0); \ fputs("\n", stderr); \ if (USE_COLOR) fputs("\x1b[31;1m", stderr); \ - fprint_inline(stderr, __VA_ARGS__); \ + Text$print(stderr, message); \ file_t *_file = (filename) ? load_file(filename) : NULL; \ if ((filename) && _file) { \ fputs("\n", stderr); \ diff --git a/src/stdlib/structs.c b/src/stdlib/structs.c index da8f1461..d1b9f824 100644 --- a/src/stdlib/structs.c +++ b/src/stdlib/structs.c @@ -126,10 +126,10 @@ PUREFUNC public Text_t Struct$as_text(const void *obj, bool colorize, const Type Text_t name = Text$from_str(type->StructInfo.name); if (type->StructInfo.is_secret || type->StructInfo.is_opaque) { - return colorize ? Texts(Text("\x1b[0;1m"), name, Text("\x1b[m(...)")) : Texts(name, Text("(...)")); + return colorize ? Text$concat(Text("\x1b[0;1m"), name, Text("\x1b[m(...)")) : Text$concat(name, Text("(...)")); } - Text_t text = colorize ? Texts(Text("\x1b[0;1m"), name, Text("\x1b[m(")) : Texts(name, Text("(")); + Text_t text = colorize ? Text$concat(Text("\x1b[0;1m"), name, Text("\x1b[m(")) : Text$concat(name, Text("(")); ptrdiff_t byte_offset = 0; ptrdiff_t bit_offset = 0; for (int i = 0; i < type->StructInfo.num_fields; i++) { diff --git a/src/stdlib/tables.c b/src/stdlib/tables.c index a801957f..400cc1b8 100644 --- a/src/stdlib/tables.c +++ b/src/stdlib/tables.c @@ -537,9 +537,9 @@ Text_t Table$as_text(const void *obj, bool colorize, const TypeInfo_t *type) { __typeof(type->TableInfo) table = type->TableInfo; if (!t) { - return table.value->size > 0 ? Texts("{", generic_as_text(NULL, false, table.key), ":", - generic_as_text(NULL, false, table.value), "}") - : Texts("{", generic_as_text(NULL, false, table.key), "}"); + return table.value->size > 0 ? Text$concat(Text("{"), generic_as_text(NULL, false, table.key), Text(":"), + generic_as_text(NULL, false, table.value), Text("}")) + : Text$concat(Text("{"), generic_as_text(NULL, false, table.key), Text("}")); } int64_t val_off = (int64_t)value_offset(type); diff --git a/src/stdlib/tables.h b/src/stdlib/tables.h index cf1c3625..93da465e 100644 --- a/src/stdlib/tables.h +++ b/src/stdlib/tables.h @@ -47,8 +47,8 @@ void *Table$get(Table_t t, const void *key, const TypeInfo_t *type); val_t *value = Table$get(t, &key, info); \ if (unlikely(value == NULL)) \ fail_source(__SOURCE_FILE__, start, end, \ - "This key was not found in the table: ", generic_as_text(&key, false, info->TableInfo.key), \ - "\n"); \ + Text$concat(Text("This key was not found in the table: "), \ + generic_as_text(&key, false, info->TableInfo.key), Text("\n"))); \ *value; \ }) #define Table$get_or_setdefault(table_expr, key_t, val_t, key_expr, default_expr, info_expr) \ diff --git a/src/stdlib/text.c b/src/stdlib/text.c index b4b27fed..411f3546 100644 --- a/src/stdlib/text.c +++ b/src/stdlib/text.c @@ -606,8 +606,8 @@ Text_t Text$middle_pad(Text_t text, Int_t width, Text_t padding, Text_t language if (padding.length == 0) fail("Cannot pad with an empty text!"); int64_t needed = Int64$from_int(width, false) - Int64$from_int(Text$width(text, language), false); - return Texts(Text$repeat_to_width(padding, needed / 2, language), text, - Text$repeat_to_width(padding, (needed + 1) / 2, language)); + return Text$concat(Text$repeat_to_width(padding, needed / 2, language), text, + Text$repeat_to_width(padding, (needed + 1) / 2, language)); } public @@ -1506,8 +1506,8 @@ Text_t Text$quoted(Text_t text, bool colorize, Text_t quotation_mark) { Text_t ret = Text$escaped(text, colorize, quotation_mark); if (!(Text$equal_values(quotation_mark, Text("\"")) || Text$equal_values(quotation_mark, Text("'")) || Text$equal_values(quotation_mark, Text("`")))) - ret = Texts("$", quotation_mark, ret, quotation_mark); - else ret = Texts(quotation_mark, ret, quotation_mark); + ret = Text$concat(Text("$"), quotation_mark, ret, quotation_mark); + else ret = Text$concat(quotation_mark, ret, quotation_mark); return ret; } @@ -1803,11 +1803,11 @@ Int_t Text$memory_size(Text_t text) { public Text_t Text$layout(Text_t text) { switch (text.tag) { - case TEXT_ASCII: return Texts(Text("ASCII("), Int64$value_as_text(text.length), Text(")")); - case TEXT_GRAPHEMES: return Texts(Text("Graphemes("), Int64$value_as_text(text.length), Text(")")); - case TEXT_BLOB: return Texts(Text("Blob("), Int64$value_as_text(text.length), Text(")")); + case TEXT_ASCII: return Text$concat(Text("ASCII("), Int64$value_as_text(text.length), Text(")")); + case TEXT_GRAPHEMES: return Text$concat(Text("Graphemes("), Int64$value_as_text(text.length), Text(")")); + case TEXT_BLOB: return Text$concat(Text("Blob("), Int64$value_as_text(text.length), Text(")")); case TEXT_CONCAT: - return Texts(Text("Concat("), Text$layout(*text.left), Text(", "), Text$layout(*text.right), Text(")")); + return Text$concat(Text("Concat("), Text$layout(*text.left), Text(", "), Text$layout(*text.right), Text(")")); default: errx(1, "Invalid text tag: %d", text.tag); } } diff --git a/src/stdlib/text.h b/src/stdlib/text.h index 9ad7441c..776ea8ec 100644 --- a/src/stdlib/text.h +++ b/src/stdlib/text.h @@ -63,8 +63,9 @@ OptionalText_t Text$cluster(Text_t text, Int_t index_int); Int_t index = index_expr; \ OptionalText_t cluster = Text$cluster(text, index); \ if (unlikely(cluster.tag == TEXT_NONE)) \ - fail_source(__SOURCE_FILE__, start, end, "Invalid text index: ", index, " (text has length ", \ - (int64_t)text.length, ")\n"); \ + fail_source(__SOURCE_FILE__, start, end, \ + Text$concat(Text("Invalid text index: "), convert_to_text(index), Text(" (text has length "), \ + convert_to_text((int64_t)text.length), Text(")\n"))); \ cluster; \ }) OptionalText_t Text$from_str(const char *str); -- cgit v1.2.3 From 63a3d9d91b3998f5558e2e22023a2bdd4caf950c Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 23 Dec 2025 14:19:26 -0500 Subject: Remove dead-ish code for debugging tables --- src/stdlib/tables.c | 49 ------------------------------------------------- 1 file changed, 49 deletions(-) diff --git a/src/stdlib/tables.c b/src/stdlib/tables.c index 400cc1b8..d417545d 100644 --- a/src/stdlib/tables.c +++ b/src/stdlib/tables.c @@ -24,14 +24,6 @@ #include "types.h" #include "util.h" -// #define DEBUG_TABLES - -#ifdef DEBUG_TABLES -#define hdebug(...) print_inline("\x1b[2m", __VA_ARGS__, "\x1b[m") -#else -#define hdebug(...) (void)0 -#endif - // Helper accessors for type functions/values: #define HASH_KEY(t, k) (generic_hash((k), type->TableInfo.key) % ((t).bucket_info->count)) #define EQUAL_KEYS(x, y) (generic_equal((x), (y), type->TableInfo.key)) @@ -76,18 +68,6 @@ PUREFUNC static INLINE size_t value_offset(const TypeInfo_t *info) { return offset; } -static INLINE void hshow(const Table_t *t) { - hdebug("{"); - for (uint32_t i = 0; t->bucket_info && i < t->bucket_info->count; i++) { - if (i > 0) hdebug(" "); - if (t->bucket_info->buckets[i].occupied) - hdebug("[", i, "]=", (uint32_t)t->bucket_info->buckets[i].index, "(", - t->bucket_info->buckets[i].next_bucket, ")"); - else hdebug("[", i, "]=_"); - } - hdebug("}\n"); -} - static void maybe_copy_on_write(Table_t *t, const TypeInfo_t *type) { if (t->entries.data_refcount != 0) List$compact(&t->entries, (int64_t)entry_size(type)); @@ -104,14 +84,10 @@ PUREFUNC public void *Table$get_raw(Table_t t, const void *key, const TypeInfo_t if (!key || !t.bucket_info) return NULL; uint64_t hash = HASH_KEY(t, key); - hshow(&t); - hdebug("Getting value with initial probe at ", hash, "\n"); bucket_t *buckets = t.bucket_info->buckets; for (uint64_t i = hash; buckets[i].occupied; i = buckets[i].next_bucket) { - hdebug("Checking against key in bucket ", i, "\n"); void *entry = GET_ENTRY(t, buckets[i].index); if (EQUAL_KEYS(entry, key)) { - hdebug("Found key!\n"); return entry + value_offset(type); } if (buckets[i].next_bucket == END_OF_CHAIN) break; @@ -130,24 +106,18 @@ PUREFUNC public void *Table$get(Table_t t, const void *key, const TypeInfo_t *ty static void Table$set_bucket(Table_t *t, const void *entry, int32_t index, const TypeInfo_t *type) { assert(t->bucket_info); - hshow(t); const void *key = entry; bucket_t *buckets = t->bucket_info->buckets; uint64_t hash = HASH_KEY(*t, key); - hdebug("Hash value (mod ", (int32_t)t->bucket_info->count, ") = ", hash, "\n"); bucket_t *bucket = &buckets[hash]; if (!bucket->occupied) { - hdebug("Got an empty space\n"); // Empty space: bucket->occupied = 1; bucket->index = index; bucket->next_bucket = END_OF_CHAIN; - hshow(t); return; } - hdebug("Collision detected in bucket ", hash, " (entry ", (uint32_t)bucket->index, ")\n"); - while (buckets[t->bucket_info->last_free].occupied) { assert(t->bucket_info->last_free > 0); --t->bucket_info->last_free; @@ -155,7 +125,6 @@ static void Table$set_bucket(Table_t *t, const void *entry, int32_t index, const uint64_t collided_hash = HASH_KEY(*t, GET_ENTRY(*t, bucket->index)); if (collided_hash != hash) { // Collided with a mid-chain entry - hdebug("Hit a mid-chain entry at bucket ", hash, " (chain starting at ", collided_hash, ")\n"); // Find chain predecessor uint64_t predecessor = collided_hash; while (buckets[predecessor].next_bucket != hash) @@ -169,19 +138,15 @@ static void Table$set_bucket(Table_t *t, const void *entry, int32_t index, const bucket->index = index; bucket->next_bucket = END_OF_CHAIN; } else { // Collided with the start of a chain, put the new entry in chain position #2 - hdebug("Hit start of a chain\n"); buckets[t->bucket_info->last_free] = (bucket_t){.occupied = 1, .index = index, .next_bucket = bucket->next_bucket}; bucket->next_bucket = t->bucket_info->last_free; } - hshow(t); } static void hashmap_resize_buckets(Table_t *t, uint32_t new_capacity, const TypeInfo_t *type) { if (unlikely(new_capacity > TABLE_MAX_BUCKETS)) fail("Table has exceeded the maximum table size (2^31) and cannot grow further!"); - hdebug("About to resize from ", t->bucket_info ? (int32_t)t->bucket_info->count : 0, " to ", new_capacity, "\n"); - hshow(t); size_t alloc_size = sizeof(bucket_info_t) + sizeof(bucket_t[new_capacity]); t->bucket_info = GC_MALLOC_ATOMIC(alloc_size); memset(t->bucket_info->buckets, 0, sizeof(bucket_t[new_capacity])); @@ -189,12 +154,8 @@ static void hashmap_resize_buckets(Table_t *t, uint32_t new_capacity, const Type t->bucket_info->last_free = new_capacity - 1; // Rehash: for (int64_t i = 0; i < (int64_t)Table$length(*t); i++) { - hdebug("Rehashing ", i, "\n"); Table$set_bucket(t, GET_ENTRY(*t, i), i, type); } - - hshow(t); - hdebug("Finished resizing\n"); } // Return address of value @@ -206,7 +167,6 @@ public void *Table$reserve(Table_t *t, const void *key, const void *value, const TypeInfo_t *type) { assert(type->tag == TableInfo); if (!t || !key) return NULL; - hshow(t); t->hash = 0; @@ -295,12 +255,10 @@ void Table$remove(Table_t *t, const void *key, const TypeInfo_t *type) { // maybe update lastfree_index1 to removed bucket's index uint64_t hash = HASH_KEY(*t, key); - hdebug("Removing key with hash ", hash, "\n"); bucket_t *bucket, *prev = NULL; for (uint64_t i = hash; t->bucket_info->buckets[i].occupied; i = t->bucket_info->buckets[i].next_bucket) { if (EQUAL_KEYS(GET_ENTRY(*t, t->bucket_info->buckets[i].index), key)) { bucket = &t->bucket_info->buckets[i]; - hdebug("Found key to delete in bucket ", i, "\n"); goto found_it; } if (t->bucket_info->buckets[i].next_bucket == END_OF_CHAIN) return; @@ -319,8 +277,6 @@ found_it:; // instead of O(N) int64_t last_entry = (int64_t)t->entries.length - 1; if (bucket->index != last_entry) { - hdebug("Removing key/value from the middle of the entries list\n"); - // Find the bucket that points to the last entry's index: uint64_t i = HASH_KEY(*t, GET_ENTRY(*t, last_entry)); while (t->bucket_info->buckets[i].index != last_entry) @@ -341,22 +297,17 @@ found_it:; int64_t bucket_to_clear; if (prev) { // Middle (or end) of a chain - hdebug("Removing from middle of a chain\n"); bucket_to_clear = (bucket - t->bucket_info->buckets); prev->next_bucket = bucket->next_bucket; } else if (bucket->next_bucket != END_OF_CHAIN) { // Start of a chain - hdebug("Removing from start of a chain\n"); bucket_to_clear = bucket->next_bucket; *bucket = t->bucket_info->buckets[bucket_to_clear]; } else { // Empty chain - hdebug("Removing from empty chain\n"); bucket_to_clear = (bucket - t->bucket_info->buckets); } t->bucket_info->buckets[bucket_to_clear] = (bucket_t){0}; if (bucket_to_clear > t->bucket_info->last_free) t->bucket_info->last_free = bucket_to_clear; - - hshow(t); } CONSTFUNC public void *Table$entry(Table_t t, int64_t n) { -- cgit v1.2.3 From 1a62de25c448d2661864ba25a98686ed506e66af Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 23 Dec 2025 15:28:05 -0500 Subject: Overhaul workflow to add automatic pushing to AUR and fix up mac builds --- .github/workflows/release.yml | 171 +++++++++++++++++++++++------------------- 1 file changed, 95 insertions(+), 76 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2be7084e..0e4f08d0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,123 +7,142 @@ on: permissions: contents: write - packages: write + +env: + PKGNAME: tomo jobs: - linux-x86_64: - runs-on: ubuntu-latest + build-linux: + strategy: + matrix: + arch: + - x86_64 + - aarch64 + + include: + - arch: x86_64 + runner: ubuntu-latest + - arch: aarch64 + runner: ubuntu-24.04-arm64 + + runs-on: ${{ matrix.runner }} + steps: - uses: actions/checkout@v4 - name: Install deps run: | sudo apt-get update - sudo apt-get install -y \ - build-essential \ - libgmp-dev \ - libunistring-dev \ - libgc-dev \ - xxd \ - binutils + sudo apt-get install -y build-essential libgmp-dev libunistring-dev libgc-dev binutils - name: Build run: | - make clean make -j - name: Package run: | - TAG=${GITHUB_REF#refs/tags/} - tar -C build -czf tomo-linux-x86_64.tar.gz tomo@${TAG} - sha256sum tomo-linux-x86_64.tar.gz > tomo-linux-x86_64.tar.gz.sha256 + TAG=${GITHUB_REF_NAME} + FILE=${PKGNAME}-linux-${{ matrix.arch }}.tar.gz + tar -C build -czf "$FILE" ${PKGNAME}@${TAG} + sha256sum "$FILE" > "$FILE.sha256" - - name: Upload - uses: softprops/action-gh-release@v2 + - name: Upload artifacts + uses: actions/upload-artifact@v3 with: - files: | - tomo-linux-x86_64.tar.gz - tomo-linux-x86_64.tar.gz.sha256 + name: linux-${{ matrix.arch }} + path: | + ${PKGNAME}-linux-${{ matrix.arch }}.tar.gz + ${PKGNAME}-linux-${{ matrix.arch }}.tar.gz.sha256 - linux-aarch64: - runs-on: ubuntu-latest + build-macos: + runs-on: macos-latest steps: - uses: actions/checkout@v4 - name: Install deps run: | - sudo apt-get update - sudo apt-get install -y \ - build-essential \ - libgmp-dev \ - libunistring-dev \ - libgc-dev \ - xxd \ - binutils + brew update + brew install gmp libunistring bdw-gc llvm binutils - - name: Build + - name: Build arm64 run: | - make clean make -j - name: Package run: | - TAG=${GITHUB_REF#refs/tags/} - tar -C build -czf tomo-linux-aarch64.tar.gz tomo@${TAG} - sha256sum tomo-linux-aarch64.tar.gz > tomo-linux-aarch64.tar.gz.sha256 + TAG=${GITHUB_REF_NAME} + tar -C build -czf ${PKGNAME}-macos-arm64.tar.gz ${PKGNAME}@${TAG} + shasum -a 256 ${PKGNAME}-macos-arm64.tar.gz > ${PKGNAME}-macos-arm64.tar.gz.sha256 - - name: Upload + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: macos-arm64 + path: | + ${PKGNAME}-macos-arm64.tar.gz + ${PKGNAME}-macos-arm64.tar.gz.sha256 + + upload-release: + needs: [build-linux, build-macos] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v3 + with: + path: release/ + + - name: List artifacts + run: ls -l release/ + + - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - files: | - tomo-linux-aarch64.tar.gz - tomo-linux-aarch64.tar.gz.sha256 + files: release/* - macos: - runs-on: macos-latest + aur: + needs: upload-release + runs-on: ubuntu-latest + env: + AUR_SSH_KEY: ${{ secrets.AUR_SSH_KEY }} steps: - uses: actions/checkout@v4 - name: Install deps run: | - brew update - brew install gmp libunistring bdw-gc llvm binutils + sudo apt-get install -y pacman-contrib jq gh - - name: Build arm64 + - name: Set up SSH run: | - make clean - make -j \ - CC=clang \ - CFLAGS="-arch arm64" \ - LDFLAGS="-arch arm64" - mv build/tomo build/tomo-arm64 - - - name: Build x86_64 + mkdir -p ~/.ssh + echo "$AUR_SSH_KEY" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts + + - name: Wait for release assets run: | - make clean - make -j \ - CC=clang \ - CFLAGS="-arch x86_64" \ - LDFLAGS="-arch x86_64" - mv build/tomo build/tomo-x86_64 - - - name: Create universal binary + TAG=${GITHUB_REF_NAME} + for i in $(seq 60); do + if gh release view "$TAG" --json assets \ + | jq -e '[ .assets[].name ] | index("tomo-linux-x86_64.tar.gz") and index("tomo-linux-aarch64.tar.gz")' >/dev/null + then exit 0; fi + sleep 10 + done + echo "Timed out waiting for release assets" + exit 1 + + - name: Update PKGBUILD run: | - lipo -create \ - build/tomo-arm64 \ - build/tomo-x86_64 \ - -output build/tomo - lipo -info build/tomo - - - name: Package + TAG=${GITHUB_REF_NAME#v} + sed -i "s/^_tomo_version=.*/_tomo_version=${TAG}/" PKGBUILD + updpkgsums + makepkg --printsrcinfo > .SRCINFO + git config user.name "GitHub Actions" + git config user.email "actions@github.com" + git diff --quiet || git commit -am "Release v${TAG}" + + - name: Push to AUR run: | - TAG=${GITHUB_REF#refs/tags/} - tar -C build -czf tomo-macos-universal.tar.gz tomo@${TAG} - shasum -a 256 tomo-macos-universal.tar.gz > tomo-macos-universal.tar.gz.sha256 - - - name: Upload - uses: softprops/action-gh-release@v2 - with: - files: | - tomo-macos-universal.tar.gz - tomo-macos-universal.tar.gz.sha256 + git push aur HEAD:master -- cgit v1.2.3