bun icon indicating copy to clipboard operation
bun copied to clipboard

Symbol not found _ucal_getHostTimeZone

Open LangLangBart opened this issue 1 year ago • 13 comments

What version of Bun is running?

1.0.3

What platform is your computer?

bunx envinfo --system

  System:
    OS: macOS 10.15.7
    CPU: (8) x64 Intel(R) Core(TM) i7-3720QM CPU @ 2.60GHz

What steps can reproduce the bug?

  • create a test.js file with this oneline
console.log(`${new Date()}`)
  • run it with bun run test.js

What is the expected behavior?

Mon Sep 25 2023 01:25:24 GMT+0200 (Central European Summer Time)

What do you see instead?

bun run test.js
dyld: lazy symbol binding failed: Symbol not found: _ucal_getHostTimeZone
  Referenced from: /usr/local/bin/bun (which was built for Mac OS X 12.0)
  Expected in: /usr/lib/libicucore.A.dylib

dyld: Symbol not found: _ucal_getHostTimeZone
  Referenced from: /usr/local/bin/bun (which was built for Mac OS X 12.0)
  Expected in: /usr/lib/libicucore.A.dylib

zsh: killed     bun run test.js

Additional information

  • the issue didn't occur with bun 1.0.2
bun --version
1.0.2

bun run test.js
Mon Sep 25 2023 01:23:56 GMT+0200 (Central European Summer Time)

[!NOTE] the issue only occurs with the embed expressions inside the string literals

# test.js
console.log(new Date())
2023-09-24T23:18:49.867Z

LangLangBart avatar Sep 24 '23 23:09 LangLangBart

Also experiencing this issue.

  % bun ./build.js --watch
    building
    dyld: lazy symbol binding failed: Symbol not found: _ucal_getHostTimeZone
      Referenced from: /Users/jq170727/.bun/bin/bun (which was built for Mac OS X 12.0)
      Expected in: /usr/lib/libicucore.A.dylib
    
    dyld: Symbol not found: _ucal_getHostTimeZone
      Referenced from: /Users/jq170727/.bun/bin/bun (which was built for Mac OS X 12.0)
      Expected in: /usr/lib/libicucore.A.dylib

Discovered 'bun upgrade' doesn't have a rollback :-( Fortunately I was able to manually re-install the previous version.

  % curl -fsSL https://bun.sh/install | bash -s "bun-v1.0.2"
    bun was installed successfully to ~/.bun/bin/bun 
    Run 'bun --help' to get started
  % bun --version
    1.0.2
  % bunx envinfo --system
    System:
    OS: macOS 10.15.7
    CPU: (8) x64 Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz

jq170727 avatar Sep 29 '23 20:09 jq170727

This issue is caused by the library /usr/lib/libicucore.dylib provided by Catalina. As you can see, it doesn't contain the required function symbol:

$ objdump -t /usr/lib/libicucore.A.dylib | grep -i gethosttimezone

The ucal_getHostTimeZone() function was added in ICU version 65 in 2019. The solution is to get a newer/complete version of the ICU library (e.g. with Nix, MacPorts, ...) and have Bun use that library instead.

This is how it looks like with a version from Nix (I went with the darwin.ICU package since it retains the same file structure, unlike the icu package):

$ objdump -t /nix/store/11mjqm402w50150jbqy2z19333bppm95-ICU-66
108/lib/libicucore.A.dylib | grep -i gethosttimezone
00000000001d387e g     F __TEXT,__text _ucal_getHostTimeZone

The newly downloaded library can then be used as follows (inspired by https://github.com/oven-sh/bun/issues/1266#issuecomment-1444311945):

DYLD_INSERT_LIBRARIES=/path/to/the/newer/libicucore.A.dylib DYLD_FORCE_FLAT_NAMESPACE=1 bun dev

For convenience, I have a script named bun in ~/.local/bin (which has the highest priority on my $PATH) with the following content - in my case the script is generated with Nix:

#!/bin/sh
export DYLD_INSERT_LIBRARIES=/nix/store/9kham4j5asbn4i45zpz3cj1nmj26bd8p-ICU-66108/lib/libicucore.A.dylib
export DYLD_FORCE_FLAT_NAMESPACE=1
/nix/store/lb477lhyhxlk5gcggnsdmj4jjba3nhw0-bun-1.0.4/bin/bun $@

Patching the bun executable with install_name_tool would probably be a somewhat cleaner solution, but despite doing so and having the correct library displayed with otool -L, Bun would still load the catalina version.

usagi-flow avatar Oct 09 '23 14:10 usagi-flow

Thanks for investigating and finding a workaround.

The ucal_getHostTimeZone() function was added in ICU version 65 in 2019.

  • the icu version seems to be 64.2.0 on macOS 10.15
otool -L /usr/lib/libicucore.A.dylib
# /usr/lib/libicucore.A.dylib:
#         /usr/lib/libicucore.A.dylib (compatibility version 1.0.0, current version 64.2.0)
#         /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 902.1.0)
#         /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1)

I tried using the brew package of the icu4c formulae, but couldn't find or generate the libicucore.A.dylib file. I haven't explored nix yet, but I'm considering it. However, I anticipate more compatibility issues in the future, so I might opt for the simpler route and switch to Ventura. The OpenCore Legacy Patcher seems promising.

LangLangBart avatar Oct 10 '23 14:10 LangLangBart

Got same error for new Date(120).getFullYear() Bun 1.0.4 Mac OS Mojave 10.14.6

al6x avatar Nov 08 '23 23:11 al6x

For convenience, I have a script named bun in ~/.local/bin

@usagi-flow. Thanks for the tip.

I've attached a Bash script below that includes the build of libicucore.A.dylib from Apple's open-source ICU repository[^1], as well as the workaround described in issue #7458.

Bash Wrapper Script

To use the script, save it as bun, make it executable with chmod +x, and add the directory where the script is located to your PATH. For example:

chmod +x $HOME/.dotfiles/scripts/bun
# .bashrc/.zshrc
export PATH="$HOME/.dotfiles/scripts:$PATH"
# make sure the bun 'script' is show when checking with `which`
which bun
# /Users/paria/.dotfiles/scripts/bun

The script currently assumes that your bun binary is located at /usr/local/bin/bun. If this is not the case, change the last line of the script to the correct location.

Running bun for the first time will generate three files in the ~/.bun/workaround_dir.

~/.bun/workaround_dir
├── icudt70l.dat
├── libicucore.A.dylib
└── pwritev.dylib
#!/usr/bin/env bash

set -o errexit -o nounset -o pipefail
# https://www.gnu.org/software/bash/manual/bash.html#The-Set-Builtin

# Bun workaround to use the latest Bun version without running in the bugs below

####################################################
# BUG 1 '_ucal_getHostTimeZone' and '_unumrf_openForSkeletonWithCollapseAndIdentityFallback'
####################################################
# https://github.com/oven-sh/bun/issues/6020#issuecomment-1753119276 (inspired by @usagi-flow)

# bun is build with macOS 12.5, which relies on ICU @70.1 (otool -L /usr/local/bin/bun)
# macOS 10.15 @64.2 https://opensource.apple.com/releases/
# '_ucal_getHostTimeZone' was added to ICU with @65, and thus is not available for macOS 10.x
# '_unumrf_openForSkeletonWithCollapseAndIdentityFallback' came with ICU @68
# ICU-70104.3: The '70' is the ICU major version, '1' is the minor version, and '04' denotes Apple's
# custom revisions since a major ICU upgrade. '3' is an optional branch version, branched from
# mainline version 70.1.04.
# This version was tested on macOS 10.15.7 (April 9, 2024) and successfully passed all checks
icu_tag="ICU-70104.3"
workaround_dir="$HOME/.bun/workaround_dir"
# dynamic ICU library
libicucore_icu_dylib="$workaround_dir/libicucore.A.dylib"
# Required for ICU services such as locales, character properties, and collation rules.
# The file only uses the two numbers from the major version, for example, 'icudt70l.dat'
icudt_dat="$workaround_dir/icudt${icu_tag:4:2}l.dat"

if [[ ! -f $libicucore_icu_dylib || ! -f $icudt_dat ]]; then

	# the temporary location of the ICU git repo
	git_dir_location="$HOME/.bun/ICU"
	[[ -d $git_dir_location ]] && rm -rf "$git_dir_location"

	[[ -d $workaround_dir ]] && rm -rf "$workaround_dir"
	#  Create directory to store files in
	mkdir -p "$workaround_dir" || exit 1

	echo
	# Check if the Xcode Command Line Tools Package has been installed, otherwise install it
	echo "[INFO] Check for the Xcode Command Line Tools Package..." >&2
	xcode-select --print-path >/dev/null || xcode-select --install
	echo
	echo "[INFO] Starting to build '$icu_tag' ~5-15min..." >&2
	echo "[INFO] This is only necessary once." >&2
	echo

	echo "[INFO] Cloning Apple's ICU Git repo into: '$git_dir_location'..." >&2
	echo
	git clone --branch ${icu_tag} --depth 1 \
		https://github.com/apple-oss-distributions/ICU.git -- "$git_dir_location"

	echo "[INFO] Apply patches..." >&2

	# When building the 'libicucore.A.dylib' file, the process ends with the error:
	# clang: error: no such file or directory: './tools/tzcode/icuzdump.o'
	# To prevent this, build the 'icuzdump.o' file.
	cp "$git_dir_location/makefile" "$git_dir_location/makefile.bak"
	if ! cat <<'EOF' | patch "$git_dir_location/makefile"; then
--- a/makefile
+++ b/makefile
@@ -1123,4 +1123,7 @@ icu debug profile : $(OBJROOT_CURRENT)/Makefile
 	echo "# start make for target"
 	date "+# %F %T %z"
+	(cd $(OBJROOT_CURRENT)/tools/tzcode; \
+		$(MAKE) $($(ENV_$@)) icuzdump.o || exit 1; \
+	);
 	(cd $(OBJROOT_CURRENT); \
 		$(MAKE) $(MAKEJOBS) $($(ENV_$@)) || exit 1; \
EOF
		echo "[INFO] Patch failed to apply, attempt to use the '--ignore-errors' flag" >&2
		mv "$git_dir_location/makefile.bak" "$git_dir_location/makefile"
		ignore_flag="--ignore-errors"
	fi

	# Disables a test that consistently fails, borrowed from the Nix setup
	# https://github.com/NixOS/nixpkgs/blob/master/pkgs/os-specific/darwin/apple-source-releases/ICU/default.nix?plain=1#L38
	perl -i -pe 's/&TestMailFilterCSS/NULL/g' "$git_dir_location/icuSources/test/cintltst/ucsdetst.c"
	# Disable consistently failing tests for 'America/Panama', fixed in later ICU versions
	perl -i -pe 's/{"America\/Cayman", "America\/Panama"}/{"America\/Panama", "America\/Cayman"}/' \
		"$git_dir_location/icuSources/test/intltest/tztest.cpp"
	perl -i -ne 'print unless /{"America\/Coral_Harbour", "America\/Panama"}/' \
		"$git_dir_location/icuSources/test/intltest/tztest.cpp"
	echo "[INFO] Build and run tests..." >&2
	pushd "$git_dir_location" || exit 1

	if ! make ${ignore_flag+$ignore_flag} check \
		DATA_LOOKUP_DIR="$workaround_dir" \
		MAC_OS_X_VERSION_MIN_REQUIRED="$(sw_vers -productVersion | awk -F. '{printf "%d%02d00\n", $1, $2}')" \
		OSX_HOST_VERSION_MIN_STRING="$(sw_vers -productVersion | awk -F. '{print $1"."$2}')" \
		SDKPATH="$(xcrun --sdk macosx --show-sdk-path)" \
		TZDATA_LOOKUP_DIR="" \
		TARGET_SPEC="$(uname -p)-apple-darwin$(uname -r)"; then

		echo "[ERROR] 'make check' failed. An false positive test case may be causing this issue." >&2
		# I don't want to add another flag or environment variable to skip exiting, as this is just
		# a wrapper for bun and the script is relatively small.
		echo "If the error seems minor, consider removing 'exit 1' below. Exiting for now..." >&2
		echo "Script location: ${BASH_SOURCE[0]}:$LINENO" >&2
		exit 1
	fi
	popd || exit 1

	if [[ ! -f $git_dir_location/build/libicucore.A.dylib ]]; then
		echo "[ERROR] Failed to create: 'libicucore.A.dylib'. Exiting..." >&2
		exit 1
	fi
	# Using 'grep -q' with 'set -o pipefail' can lead to premature pipe closure upon the first
	# match, causing 'objdump' to exit with a non-zero status due to the closed reading end of the
	# pipe. To circumvent this, we employ 'grep' without '-q' and suppress the output by redirecting
	# it to '>/dev/null'. More details: https://bugzilla.redhat.com/show_bug.cgi?id=1589997
	if ! objdump -t "$git_dir_location/build/libicucore.A.dylib" | grep _ucal_getHostTimeZone >/dev/null; then
		echo "[ERROR] 'libicucore.A.dylib' was created, but the '_ucal_getHostTimeZone' is still not there. Exiting..." >&2
		exit 1
	fi

	# Move the files to the expected location
	mv -f "$git_dir_location/build/libicucore.A.dylib" "$libicucore_icu_dylib" || exit 1

	if [[ ! -f $git_dir_location/build/data/out/icudt${icu_tag:4:2}l.dat ]]; then
		echo "[ERROR] Failed to create: 'icudt${icu_tag:4:2}l.dat'. Exiting..." >&2
		exit 1
	fi
	mv -f "$git_dir_location/build/data/out/icudt${icu_tag:4:2}l.dat" "$icudt_dat" || exit 1

	# Remove the Git repository as it's no longer needed
	rm -rf "$git_dir_location"

	echo "[SUCCESS] Created dynamic library '$libicucore_icu_dylib'." >&2
	afplay /System/Library/Sounds/Pop.aiff || true
fi

####################################################
# BUG 2 '_pwritev$NOCANCEL'
####################################################
# https://github.com/oven-sh/bun/issues/7458#issuecomment-1996348056 (thanks to @kzc)

pwritev_dylib="$workaround_dir/pwritev.dylib"

if [[ ! -f $pwritev_dylib ]]; then
	pwritev_file="$workaround_dir/pwritev.c"
	cat <<'EOF' >"$pwritev_file"
#include <sys/uio.h>
#include <unistd.h>
ssize_t pwritev$NOCANCEL(int fd, const struct iovec *iov, int iovcnt, off_t offset) {
    ssize_t written;
    off_t origPos = lseek(fd, 0, SEEK_CUR);
    if (origPos >= 0
        && lseek(fd, offset, SEEK_SET) >= 0
        && (written = writev(fd, iov, iovcnt)) >= 0
        && lseek(fd, origPos, SEEK_SET) >= 0) {
            return written;
    }
    return -1;
}
EOF
	# create the dynamic library
	if ! cc -dynamiclib -O2 "$pwritev_file" -o "$pwritev_dylib"; then
		echo "[ERROR] Failed to create: '$pwritev_dylib'. Exiting..." >&2
		exit 1
	fi
	# Delete the file as we no longer need it
	rm -f "$pwritev_file"
	echo "[SUCCESS] Created dynamic library '$pwritev_dylib'." >&2
	afplay /System/Library/Sounds/Pop.aiff || true
fi

####################################################
# BUN setup
####################################################

export DYLD_INSERT_LIBRARIES="$pwritev_dylib:$libicucore_icu_dylib"
export DYLD_FORCE_FLAT_NAMESPACE=1
# NOTE: One might have to change the path if installed differently than through Homebrew.
/usr/local/bin/bun "$@"

EDIT1: fix TODO and update to ICU-66112 EDIT2: add DATA_LOOKUP_DIR EDIT3 include make check and update to ICU-68232.1 EDIT4: wrap make check in if condition EDIT5 update to ICU-70104.3 (which is also used by the official bun binary (otool -L /usr/local/bin/bun)

[!WARNING] Tested with bun version 1.1.3 on macOS 10.15. Limitations encountered with this setup:

  1. bun repl command can not be used, as mentioned in the comment[^2] by @kzc.

[^1]: GitHub - apple-oss-distributions/ICU at ICU-70104.3 [^2]: v1.0.15 produce error /usr/lib/libSystem.B.dylib, v1.0.14 worked fine · Issue #7458 · oven-sh/bun · GitHub

LangLangBart avatar Apr 05 '24 18:04 LangLangBart

On my mac at least the unresolved _ucal_getHostTimeZone symbol can be worked around without a new dylib just by setting the TZ environment variable...

given:

$ echo `sw_vers -productName && sw_vers -productVersion && uname -m`
Mac OS X 10.14.6 x86_64

$ bun --revision
1.1.3+c739c4ade

$ bun --print 'new Date(1.262e11).toString()'
dyld: lazy symbol binding failed: Symbol not found: _ucal_getHostTimeZone
  Referenced from: /usr/local/bin/bun (which was built for Mac OS X 11.0)
  Expected in: /usr/lib/libicucore.A.dylib

dyld: Symbol not found: _ucal_getHostTimeZone
  Referenced from: /usr/local/bin/bun (which was built for Mac OS X 11.0)
  Expected in: /usr/lib/libicucore.A.dylib

Killed: 9

workaround:

$ unset DYLD_INSERT_LIBRARIES DYLD_FORCE_FLAT_NAMESPACE TZ

$ TZ=UTC bun --print 'new Date(1.262e11).toString()'
Mon Dec 31 1973 15:33:20 GMT+0000 (Coordinated Universal Time)

$ TZ=Australia/Eucla bun --print 'new Date(1.262e11).toString()'
Tue Jan 01 1974 00:18:20 GMT+0845 (Australian Central Western Standard Time)

$ TZ=America/Argentina/Buenos_Aires bun --print 'new Date(1.262e11).toString()'
Mon Dec 31 1973 12:33:20 GMT-0300 (Argentina Standard Time)

$ TZ=NZ bun --print 'new Date(1.262e11).toString()'
Tue Jan 01 1974 03:33:20 GMT+1200 (New Zealand Standard Time)
$ TZ=US/Pacific bun -e 'console.log(`${new Date()}`)'
Mon Apr 08 2024 21:19:00 GMT-0700 (Pacific Daylight Time)

Perhaps there's a counter example for ucal_getHostTimeZone that doesn't work with TZ in which case the new ICU dylib is best.

Note: the _pwritev$NOCANCEL bug is a different matter and still requires its own workaround.

kzc avatar Apr 09 '24 04:04 kzc

just by setting the TZ environment variable...

This solution also works for me. Thank you for looking into it.


However, I've encountered another issue related to the ICU library. For example,

/usr/local/bin/bun --print "1234567.89.toLocaleString()"
dyld: lazy symbol binding failed: Symbol not found: _unumrf_openForSkeletonWithCollapseAndIdentityFallback
  Referenced from: /usr/local/bin/bun (which was built for Mac OS X 11.0)
  Expected in: /usr/lib/libicucore.A.dylib

dyld: Symbol not found: _unumrf_openForSkeletonWithCollapseAndIdentityFallback
  Referenced from: /usr/local/bin/bun (which was built for Mac OS X 11.0)
  Expected in: /usr/lib/libicucore.A.dylib

[1]    64305 killed     /usr/local/bin/bun --print "1234567.89.toLocaleString()"

Building a new ICU library as previously described resolves this issue as well.

Some ICU Tests for Bun
import { describe, expect, test } from 'bun:test'
import process from 'bun'

describe('Date', () => {
	test('toLocaleString  Japanese locale', () => {
		const now = new Date()
		expect(now.toLocaleString('ja-JP', { era: 'long', year: 'numeric', month: 'long', day: 'numeric' })).toMatch(/年/)
	})

	test('toLocaleString with time zone', () => {
		const eventDate = new Date()
		const formattedDate = eventDate.toLocaleString('en-US', {
			timeZone: 'Asia/Tokyo', hour12: true, hour: '2-digit', minute: '2-digit'
		})
		expect(formattedDate).toMatch(/AM|PM/)
	})

	test('toLocaleString TimeZones: January 1, 1900/2000 at 05:00:00 UTC', () => {
		let date = new Date(Date.UTC(1900, 0, 1, 5, 0, 0))

		expect(date).toBeDate()
		let timeZone = 'Pacific/Honolulu'
		expect(date.toLocaleString('en-us', { year: 'numeric', timeZone })).toBe('1899')
		expect(date.toLocaleString('en-us', { month: 'long', timeZone })).toBe('December')
		expect(date.toLocaleString('en-us', { day: '2-digit', timeZone })).toBe('31')
		expect(date.toLocaleString('en-us', { hour: '2-digit', hour12: false, timeZone })).toBe('18')
		expect(date.toLocaleString('en-us', { minute: '2-digit', timeZone })).toBe('30')

		timeZone = 'Europe/Zurich'
		expect(date.toLocaleString('en-us', { year: 'numeric', timeZone })).toBe('1900')
		expect(date.toLocaleString('en-us', { month: 'long', timeZone })).toBe('January')
		expect(date.toLocaleString('en-us', { day: '2-digit', timeZone })).toBe('01')
		expect(date.toLocaleString('en-us', { hour: '2-digit', hour12: false, timeZone })).toBe('06')
		expect(date.toLocaleString('en-us', { minute: '2-digit', timeZone })).toBe('0')

		date = new Date(Date.UTC(2000, 0, 1, 5, 0, 0))
		timeZone = 'Pacific/Honolulu'
		expect(date.toLocaleString('en-us', { year: 'numeric', timeZone })).toBe('1999')
		expect(date.toLocaleString('en-us', { month: 'long', timeZone })).toBe('December')
		expect(date.toLocaleString('en-us', { day: '2-digit', timeZone })).toBe('31')
		expect(date.toLocaleString('en-us', { hour: '2-digit', hour12: false, timeZone })).toBe('19')
		// Hawaii changed from UTC-10:30 to UTC-10:00 in 1947
		expect(date.toLocaleString('en-us', { minute: '2-digit', timeZone })).toBe('0')

		timeZone = 'Europe/Zurich'
		expect(date.toLocaleString('en-us', { year: 'numeric', timeZone })).toBe('2000')
		expect(date.toLocaleString('en-us', { month: 'long', timeZone })).toBe('January')
		expect(date.toLocaleString('en-us', { day: '2-digit', timeZone })).toBe('01')
		expect(date.toLocaleString('en-us', { hour: '2-digit', hour12: false, timeZone })).toBe('06')
		expect(date.toLocaleString('en-us', { minute: '2-digit', timeZone })).toBe('0')
	})

	test('Template literals [unset TZ]', () => {
		process.env.TZ = ''
		const dateString = `${new Date('April 7, 2024 12:00:00 GMT+0000')}`
		expect(dateString).toBeString()
	})

	test('Template literals [GMT]', () => {
		process.env.TZ = 'Etc/GMT'
		const dateString = `${new Date('April 7, 2024 12:00:00 GMT+0000')}`

		expect(dateString).toContain('Sun Apr 07 2024')
		expect(dateString).toContain('12:00:00 GMT+0000')
	})

	test('Template literals [NZ]', () => {
		process.env.TZ = 'NZ'
		const dateString = `${new Date('April 7, 2024 12:00:00 GMT+0000')}`

		expect(dateString).toContain('Mon Apr 08 2024')
		expect(dateString).toContain('00:00:00 GMT+1200')
	})
})

describe('Intl', () => {
	test('Collator', () => {
		const string1 = 'resume'
		const string2 = 'resumé'
		const collator = new Intl.Collator('fr', { sensitivity: 'base' })
		expect(collator.compare(string1, string2)).toBe(0)
	})
})

describe('Intl', () => {
	test('DateTimeFormat with different locales and options', () => {
		const date = new Date('2024-05-15T14:30:00Z')
		const originalTimeZone = process.env.TZ
		process.env.TZ = 'Etc/GMT'

		const formatter1 = new Intl.DateTimeFormat('en-US', {
			weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric', timeZoneName: 'short'
		})
		expect(formatter1.format(date)).toBe('Wednesday, May 15, 2024, 2:30 PM UTC')

		const formatter2 = new Intl.DateTimeFormat('de-DE', {
			weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric', timeZoneName: 'short'
		})
		expect(formatter2.format(date)).toBe('Mittwoch, 15. Mai 2024, 14:30 UTC')

		process.env.TZ = originalTimeZone
	})
})

describe('Intl', () => {
	test('DisplayNames basic functionality', () => {
		const displayNames = new Intl.DisplayNames(['en'], { type: 'language' })
		expect(displayNames.of('fr')).toBe('French')
	})
})

describe('Intl', () => {
	test('getCanonicalLocales', () => {
		expect(Intl.getCanonicalLocales('EN-US')).toEqual(['en-US'])
		expect(Intl.getCanonicalLocales(['en-GB', 'en-US'])).toEqual(['en-GB', 'en-US'])
	})
})

describe('Intl', () => {
	test('ListFormat basic functionality', () => {
		const listFormat = new Intl.ListFormat('en', { style: 'long', type: 'conjunction' })
		expect(listFormat.format(['apple', 'banana', 'cherry'])).toBe('apple, banana, and cherry')
	})
})

describe('Intl', () => {
	test('Locale', () => {
		const locale = new Intl.Locale('en-US')
		expect(locale.language).toBe('en')
		expect(locale.region).toBe('US')
	})
})

describe('Intl', () => {
	test('NumberFormat', () => {
		const number = 123_456.789
		expect(new Intl.NumberFormat('en-US').format(number)).toBe('123,456.789')
		expect(new Intl.NumberFormat('de-DE').format(number)).toBe('123.456,789')
	})
})

describe('Intl', () => {
	test('PluralRules with different locales', () => {
		const pr1 = new Intl.PluralRules('en-US')
		expect(pr1.select(0)).toBe('other')
		expect(pr1.select(1)).toBe('one')
		expect(pr1.select(2)).toBe('other')

		const pr2 = new Intl.PluralRules('ar')
		expect(pr2.select(0)).toBe('zero')
		expect(pr2.select(1)).toBe('one')
		expect(pr2.select(2)).toBe('two')
		expect(pr2.select(5)).toBe('few')
		expect(pr2.select(11)).toBe('many')
	})
})

describe('Intl', () => {
	test('RelativeTimeFormat', () => {
		const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })
		expect(rtf.format(-1, 'day')).toBe('yesterday')
		expect(rtf.format(2, 'day')).toBe('in 2 days')
	})
})

describe('Intl', () => {
	test('Segmenter with different locales and granularities', () => {
		const text = 'Hello, world! This is a test.'

		const segmenter1 = new Intl.Segmenter('en', { granularity: 'word' })
		const segments1 = [...segmenter1.segment(text)].map(segment => segment.segment)
		expect(segments1).toEqual(['Hello', ',', ' ', 'world', '!', ' ', 'This', ' ', 'is', ' ', 'a', ' ', 'test', '.'])
	})

	test('Segmenter with emojis', () => {
		const text = '🌍 Hello 🌎 world 🌏! 👋 This is a 🧪 test 📝 with emojis 😄👍'

		const segmenter1 = new Intl.Segmenter('en', { granularity: 'word' })
		const segments1 = [...segmenter1.segment(text)].map(segment => segment.segment)
		expect(segments1).toEqual(['🌍', ' ', 'Hello', ' ', '🌎', ' ', 'world', ' ', '🌏', '!', ' ', '👋', ' ', 'This', ' ', 'is', ' ', 'a', ' ', '🧪', ' ', 'test', ' ', '📝', ' ', 'with', ' ', 'emojis', ' ', '😄', '👍'])
	})
})

describe('Intl', () => {
	test('supportedValuesOf', () => {
		const locales = Intl.supportedValuesOf('unit')
		expect(locales).toContain('celsius')
		expect(locales).toContain('gigabyte')
	})
})

describe('Number', () => {
	test('toLocaleString', () => {
		const number = 1_234_567.89

		expect(number.toLocaleString()).toMatch(/,/)
		expect(number.toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })).toContain('€')
		expect(number.toLocaleString('en-US', { notation: 'compact', compactDisplay: 'short' })).toContain('M')
	})
})

describe('String', () => {
	test('localeCompare', () => {
		const string1 = 'resume'
		const string2 = 'resumé'

		expect(string1.localeCompare(string2) !== 0).toBe(true)
		expect(string1.localeCompare(string2, 'en-US', { sensitivity: 'base' })).toBe(0)
	})

	test('Array sorting with localeCompare in Swedish locale', () => {
		const fruits = ['äpple', 'banana', 'cherry', 'apple']
		fruits.sort((a, b) => a.localeCompare(b, 'sv', { sensitivity: 'base' }))
		expect(fruits).toEqual(['apple', 'banana', 'cherry', 'äpple'])
	})
})

EDIT1: Added more tests EDIT2: Sort tests

LangLangBart avatar Apr 09 '24 07:04 LangLangBart

@LangLangBart Good stuff. I agree building and using a new ICU dylib is the correct way to go about it.

I also think it would be useful if bun were to support minimum viable fallback functions for these issues and pwritev$NOCANCEL just to avoid crashing on a recently unsupported OS version. That approach wouldn't preclude using newer shared libraries with correct fixes if they become available.

$ bun --print '[1234567.89.toLocaleString("de-DE"),new Date().toString()]'
dyld: lazy symbol binding failed: Symbol not found: _unumrf_openForSkeletonWithCollapseAndIdentityFallback
  Referenced from: /usr/local/bin/bun (which was built for Mac OS X 11.0)
  Expected in: /usr/lib/libicucore.A.dylib

dyld: Symbol not found: _unumrf_openForSkeletonWithCollapseAndIdentityFallback
  Referenced from: /usr/local/bin/bun (which was built for Mac OS X 11.0)
  Expected in: /usr/lib/libicucore.A.dylib

Killed: 9
$ cat polyhack.c
int ucal_getHostTimeZone() { return 0; }
void* unumrf_openForSkeletonWithCollapseAndIdentityFallback() { return 0; }
$ cc -dynamiclib -O polyhack.c -o polyhack.dylib
$ export DYLD_INSERT_LIBRARIES=`pwd`/polyhack.dylib DYLD_FORCE_FLAT_NAMESPACE=1
$ bun --print '[1234567.89.toLocaleString("de-DE"),new Date().toString()]'
[ "1.234.567,89", "Tue Apr 09 2024 13:07:15 GMT+0000 (GMT)"
]

kzc avatar Apr 09 '24 13:04 kzc

@LangLangBart side question... do the ICU tests fail on your machine if the default TZ is not GMT?

tests pass with TZ=GMT:

$ unset TZ
$ DYLD_INSERT_LIBRARIES=`pwd`/polyhack.dylib DYLD_FORCE_FLAT_NAMESPACE=1 bun test bun-issue-6020-icu.test.js 
bun test v1.1.3 (c739c4ad)

bun-issue-6020-icu.test.js:
✓ ICU Functionality Tests > String localeCompare functionality [0.87ms]
✓ ICU Functionality Tests > Array sorting with localeCompare in Swedish locale [0.61ms]
✓ ICU Functionality Tests > Date toLocaleString functionality with Japanese locale [16.31ms]
✓ ICU Functionality Tests > Number toLocaleString functionality [0.60ms]
✓ ICU Functionality Tests > Date toLocaleString with time zone [0.98ms]
✓ ICU Functionality Tests > Specific Date toString in template literals [0.38ms]
✓ ICU Functionality Tests > Relative time formatting [0.39ms]

 7 pass
 0 fail
 12 expect() calls
Ran 7 tests across 1 files. [33.00ms]

test failure with TZ=NZ

$ TZ=NZ DYLD_INSERT_LIBRARIES=`pwd`/polyhack.dylib DYLD_FORCE_FLAT_NAMESPACE=1 bun test bun-issue-6020-icu.test.js  
bun test v1.1.3 (c739c4ad)

bun-issue-6020-icu.test.js:
✓ ICU Functionality Tests > String localeCompare functionality [0.69ms]
✓ ICU Functionality Tests > Array sorting with localeCompare in Swedish locale [0.67ms]
✓ ICU Functionality Tests > Date toLocaleString functionality with Japanese locale [16.16ms]
✓ ICU Functionality Tests > Number toLocaleString functionality [0.52ms]
✓ ICU Functionality Tests > Date toLocaleString with time zone [0.86ms]
48 | 		// Use a fixed date
49 | 		const dateString = `${new Date('April 7, 2024 12:00:00 GMT+0000')}`
50 | 
51 | 		// Since we know the exact date, we can be more specific in what we expect.
52 | 		// This test will focus on the presence of key date components in the output.
53 | 		expect(dateString).toContain('Sun Apr 07 2024')
       ^
error: expect(received).toContain(expected)

Expected to contain: "Sun Apr 07 2024"
Received: "Mon Apr 08 2024 00:00:00 GMT+1200 (New Zealand Standard Time)"

      at /private/tmp/bun-issue-6020-icu.test.js:53:3
✗ ICU Functionality Tests > Specific Date toString in template literals [0.57ms]
✓ ICU Functionality Tests > Relative time formatting [0.41ms]

 6 pass
 1 fail
 11 expect() calls
Ran 7 tests across 1 files. [35.00ms]

kzc avatar Apr 09 '24 14:04 kzc

side question... do the ICU tests fail on your machine if the default TZ is not GMT?

Yes, I should have set the time zone to 'Etc/GMT' in the test. I updated the test file, in my earlier comment[^1] and also added some new tests.

I tried your solution for polyhack.c, and it worked until I ran into another issue.

/usr/local/bin/bun --print "[...new Intl.Segmenter('en', { granularity: 'word' }).segment('Hello, world! This is a test.')].map(x=>x.segment)"
dyld: lazy symbol binding failed: Symbol not found: _ubrk_clone
  Referenced from: /usr/local/bin/bun
  Expected in: flat namespace

dyld: Symbol not found: _ubrk_clone
  Referenced from: /usr/local/bin/bun
  Expected in: flat namespace

[1]    34891 killed     /usr/local/bin/bun --print

I agree building and using a new ICU dylib is the correct way to go about it.

I updated the script[^2] to build ICU-70104.3, the same version that is also being used to build bun. This ICU version shipped with macOS 12[^3], this OS version seems to be what bun uses to build the binary.

otool -L /usr/local/bin/bun
/usr/local/bin/bun:
        /usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 1.0.0)
        /usr/lib/libicucore.A.dylib (compatibility version 1.0.0, current version 70.1.0)
        /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1300.23.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.100.3)

[^1]: Issue #6020 (comment ICU tests) [^2]: Issue #6020 (comment Bash Wrapper Script) [^3]: Apple Open Source

LangLangBart avatar Apr 09 '24 18:04 LangLangBart

I tried your solution for polyhack.c, and it worked until I ran into another issue.

It's like whack-a-mole trying to keep an unsupported OS version working...

$ cat polyhack.c
#include <unistd.h>
extern void* ubrk_safeClone(const void *bi, void*, int32_t* pBufferSize, void *status);
void* ubrk_clone(const void *bi, void *status) { return ubrk_safeClone(bi, 0, 0, status); }
int ucal_getHostTimeZone() { return 0; }
void* unumrf_openForSkeletonWithCollapseAndIdentityFallback() { return 0; }
$ cc -dynamiclib -O polyhack.c /usr/lib/libicucore.A.dylib -o polyhack.dylib
$ DYLD_INSERT_LIBRARIES=`pwd`/polyhack.dylib DYLD_FORCE_FLAT_NAMESPACE=1 bun --print "[...new Intl.Segmenter('en', { granularity: 'word' }).segment('Hello, world! This is a test.')].map(x=>x.segment)"
[
  "Hello", ",", " ", "world", "!", " ", "This", " ", "is", " ", "a", " ", "test", "."
]

Did the _ubrk_clone problem also occur in ICU-68232?

All your new ICU tests pass with the updated polyhack except for Intl.PluralRules which crashes with Symbol not found: _uplrules_selectFormatted. I may look at it later. Where were these tests found?

kzc avatar Apr 09 '24 22:04 kzc

Did the _ubrk_clone problem also occur in ICU-68232?

I'm not sure, as I didn't test it with ICU 68 before building ICU 70.1. If it's important, I can check.

All your new ICU tests pass with the updated polyhack except for Intl.PluralRules which crashes with Symbol not found: _uplrules_selectFormatted.

I added another test.

/usr/local/bin/bun --print "new Intl.ListFormat().format(['apples', 'bananas', 'oranges'])"
dyld: lazy symbol binding failed: Symbol not found: _ulistfmt_openForType
  Referenced from: /usr/local/bin/bun (which was built for Mac OS X 11.0)
  Expected in: /usr/lib/libicucore.A.dylib

dyld: Symbol not found: _ulistfmt_openForType
  Referenced from: /usr/local/bin/bun (which was built for Mac OS X 11.0)
  Expected in: /usr/lib/libicucore.A.dylib

[1]    88721 killed     /usr/local/bin/bun --print

Where were these tests found?

Mozilla: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/intl/pluralrules

I made a little fzf-js builtin explorer with a hotkey to open the desired method in the browser (works for most of them).


JS Builtins Explorer
#!/usr/bin/env bun
/**
 * This is a simple test utilizing bun and fzf as a Explorer for JS built-ins
 * To execute this script, adhere to the following steps:
 * 1. Verify that 'bun' and 'fzf' are installed.
 * 2. Make the script executable using chmod +x <path>.
 * 3. Execute the script from the terminal by typing the path and pressing enter.
 */

import { appendFile, unlink } from 'node:fs/promises'
import { exit, platform, ppid } from 'node:process'
import process, { file, main, spawnSync } from 'bun'

const errorFileLocation = '/tmp/fetch_bun_fzf_debug.txt'
// Alternative: https://bun.sh/blog/bun-v1.0.31#new-util-styletext
const [blue, green, yellow, reset] = ['\u001B[34m', '\u001B[32m', '\u001B[33m', '\u001B[0m']

// ░█░█░█▀▀░█░░░█▀█░█▀▀░█▀▄░░░█▀▀░█░█░█▀█░█▀▀░▀█▀░▀█▀░█▀█░█▀█░█▀▀
// ░█▀█░█▀▀░█░░░█▀▀░█▀▀░█▀▄░░░█▀▀░█░█░█░█░█░░░░█░░░█░░█░█░█░█░▀▀█
// ░▀░▀░▀▀▀░▀▀▀░▀░░░▀▀▀░▀░▀░░░▀░░░▀▀▀░▀░▀░▀▀▀░░▀░░▀▀▀░▀▀▀░▀░▀░▀▀▀

/**
 * Send updates to fzf
 */
async function postData(input: string) {
	try {
		if (!process.env.FZF_PORT)
			throw new Error('Error: FZF_PORT environment variable not assigned!')

		// NOTE: Don't use 'localhost' with bun: https://github.com/oven-sh/bun/issues/1425
		const response = await fetch(`http://127.0.0.1:${process.env.FZF_PORT}`, {
			method: 'POST',
			body: input
		})
		if (!response.ok || response.status !== 200) {
			const errorText = await response.text()
			throw new Error(`Error: ${response.status} ${response.statusText}, ${errorText}`)
		}
	}
	catch (error) {
		await appendFile(errorFileLocation, `${new Date().toISOString()} - ${error}\n ${(error as Error).stack}`)
	}
}

/**
 * Opens a given URL in the default web browser
 * @param   {string}  url
 */
function openLinkInBrowser(url: string) {
	switch (platform) {
		case 'darwin': {
			spawnSync(['open', url])
			break
		}
		case 'win32': {
			spawnSync(['start', url])
			break
		}
		default: {
			spawnSync(['xdg-open', url])
		}
	}
}

/**
 * list properties of both the constructor function itself and its prototype for built-in objects
 */
function getAllPropertiesResponse() {
	//  find all global elements and sort strings regardless of their case
	const globalObjects = Object.getOwnPropertyNames(globalThis)
		.sort((a, b) => a.localeCompare(b))
		.filter(name => !/^(global|globalThis|self)$/.test(name))

	// unique elements
	const results: Set<string> = new Set()

	for (const objectName of globalObjects) {
		const object = (globalThis as any)[objectName]

		let directory = 'js'
		// mozilla.org lists them under 'Web/API' instead of 'Web/Javascript'
		if (/^(abort|blob|broadcastchannel|btoa|bytelengthqueuingstrategy|cleartimeout|clearinterval|closeevent|console|countqueuingstrategy|crypto|customevent|domexception|errorevent|event|fetch|file|formdata|headers|message|navigator|queuemicrotask|readable|request|response|reporterror|settimeout|setinterval|structuredclone|subtlecrypto|text(en|de)coder|transformstream|url|websocket|worker|writablestream)/i.test(objectName))
			directory = 'api'
		// bun related
		else if (/^(bun|htmlrewriter)$/i.test(objectName))
			directory = 'bun'
		// ignore them
		else if (/^(builderror|buildmessage|loader|resolveerror|resolvemessage|shadowrealm)$/i.test(objectName))
			continue
		// they are listed under EventTarget already, so we just ignore them
		else if (/^(addeventlistener|dispatchevent|removeeventlistener)$/i.test(objectName))
			continue
		// https://nodejs.org/api/
		else if (/^(buffer|performance|process)$/i.test(objectName))
			directory = 'node'
		// mozilla.org lists them under WebAssembly/JavaScript_interface
		else if (/^webassembly$/i.test(objectName))
			directory = 'wasm'
		// mozilla.org lists them under 'Web/API/Window'
		else if (/^(alert|clearimmediate|confirm|postmessage|prompt)$/i.test(objectName))
			directory = 'window'

		// Ensure directory string has a minimum length of 6 characters
		directory = directory.padEnd(6)

		try {
			for (const property of Object.getOwnPropertyNames(object)) {
				if (property === 'prototype')
					continue
				else if (/^(name|length)$/i.test(property))
					results.add(`${directory} ${yellow}${objectName}${reset}`)
				else
					results.add(`${directory} ${yellow}${objectName}${reset} ${green}${property}${reset}`)
			}
		}
		catch {}

		try {
			for (const property of Object.getOwnPropertyNames(object.prototype)) {
				if (property === 'constructor') {
					continue
				}
				else if (/^(name|length)$/i.test(property)) {
					if (results.has(`${directory} ${yellow}${objectName}${reset}`))
						continue
					else
						results.add(`${directory} ${blue}${objectName}${reset}`)
				}
				else {
					results.add(`${directory} ${blue}${objectName}${reset} ${green}${property}${reset}`)
				}
			}
		}
		catch {}
	}
	return [...results].join('\n')
}

// ░█░░░█▀▀░▀█▀░▀░█▀▀░░░█▀▄░█▀▀░█▀▀░▀█▀░█▀█
// ░█░░░█▀▀░░█░░░░▀▀█░░░█▀▄░█▀▀░█░█░░█░░█░█
// ░▀▀▀░▀▀▀░░▀░░░░▀▀▀░░░▀▀░░▀▀▀░▀▀▀░▀▀▀░▀░▀

if (process.argv.includes('--browser')) {
	try {
		const prefixUrl = 'https://developer.mozilla.org/en-US/docs'
		const [directory, globalObject, property] = process.argv.at(-1)?.split(/\s+/).map(x => x.trim().toLowerCase()) ?? ''
		switch (directory) {
			case 'api': {
				if (property)
					openLinkInBrowser(`${prefixUrl}/Web/Api/${globalObject}/${property}`)
				else
					openLinkInBrowser(`${prefixUrl}/Web/Api/${globalObject}`)
				break
			}
			case 'bun': {
				openLinkInBrowser('https://bun.sh/docs')
				break
			}
			case 'js': {
				if (property)
					openLinkInBrowser(`${prefixUrl}/Web/JavaScript/Reference/Global_Objects/${globalObject}/${property}`)
				else
					openLinkInBrowser(`${prefixUrl}/Web/JavaScript/Reference/Global_Objects/${globalObject}`)
				break
			}
			case 'node': {
				if (globalObject === 'performance')
					openLinkInBrowser(`https://nodejs.org/api/perf_hooks.html#${globalObject}${property}`)
				else
					openLinkInBrowser(`https://nodejs.org/api/${globalObject}.html#${globalObject}${property}`)
				break
			}
			case 'wasm': {
				openLinkInBrowser(`${prefixUrl}/Webassembly/JavaScript_interface/${property}`)
				break
			}
			case 'window': {
				openLinkInBrowser(`${prefixUrl}/Web/Api/Window/${globalObject}`)
				break
			}
		}
	}
	catch (error) {
		await appendFile(errorFileLocation, `${new Date().toISOString()} - ${error}\n ${(error as Error).stack}`)
	}
}
else if (process.argv.includes('--preview')) {
	const colorInfoDescription = `${yellow}Yellow: Static ${blue}Blue: Prototype ${green}Green: Method${reset}`

	// This is a demonstration making use of the '--listen' flag with fzf
	await postData(`change-preview:
        echo "${colorInfoDescription}"
		echo Process Status: $PPID
		echo
		ps -eo "pcpu,pmem,etime,ucomm" $PPID`)
}
else {
	const header = '^b browser'
	// As a rule of thumb, the asynchronous `Bun.spawn` API is better for HTTP servers and apps, and
	// `Bun.spawnSync` is better for building command-line tools.
	spawnSync([
		'fzf',
		'--color', 'hl:-1:underline,hl+:-1:underline:reverse',
		'--ansi', '--reverse', '--listen',
		'--bind', `ctrl-b:execute-silent:${main} --browser {}`,
		'--bind', `tab:change-preview-window(default:wrap:70%|hidden)+execute-silent:${main} --preview`,
		'--nth', '2,3,1',
		'--prompt', 'JavaScript Global Built-Ins > ',
		`--query=${process.argv[2] ?? ''}`,
		'--header', header,
		'--with-nth', '2..',
		'--preview-window', 'hidden'], {
		stdio: [new Response(getAllPropertiesResponse()), 'inherit', 'inherit'],
		onExit: async (_, exitCode) => {
			if (await file(errorFileLocation).exists()) {
				console.error('While running the scripts, some errors were logged:')
				console.error(await file(errorFileLocation).text())
				// no Bun API for deleting files (Mar '24)
				// https://bun.sh/guides/write-file/unlink
				await unlink(errorFileLocation)
				exit(1)
			}
			if (exitCode)
				exit(exitCode)
		}
	})
}

LangLangBart avatar Apr 09 '24 23:04 LangLangBart

I didn't test it with ICU 68 before building ICU 70.1. If it's important, I can check.

It's not important. Just was wondering if the ICU upgrade was as result of something not working.

Mozilla: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/intl/pluralrules

Thanks.

kzc avatar Apr 09 '24 23:04 kzc

Mozilla: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/intl/pluralrules

Thanks.

@kzc I went through the ECMAScript's official conformance test suite[^1].

I did not discover any other missing symbols. However, I ran it on 10.15 (ICU 64.2), so you may encounter some additional missing symbols, like the one you previously mentioned, _uplrules_selectFormatted. This symbol is not missing for me because it was introduced with ICU 64.

The polyfill hacks are not necessary for me since I have built the ICU 70.1 library. However, I will try it out if you decide to proceed. I just wanted to inform you.

The command I used:

git clone https://github.com/tc39/test262.git

bun install --global test262-harness

# ~5mins
touch debug.log
find test/intl402 -type f -name "*.js" ! -path "test/intl402/Temporal/*" -print0 |
  xargs -0 -n1 -P "$(sysctl -n hw.ncpu)" bash -c $'
    test262-harness --host-type node --host-path /usr/local/bin/bun "$0" |
        grep "^dyld: Symbol not found" |
        while read -r line; do
            if ! grep -Fxq "$line" debug.log; then
                (
                    echo "$0"
                    echo "$line"
                    echo
                ) >>debug.log
            fi
        done'

cat debug.log

EDIT1: Update command to ignore the Temporal folder (currently not supported)

Missing Symbol ICU version needed
_uplrules_selectFormatted 64
_ucal_getHostTimeZone 65
_ulistfmt_openForType 67
_unumrf_openForSkeletonWithCollapseAndIdentityFallback 68
_ubrk_clone 69

Others

  • _pwritev$NOCANCEL

[^1]: test262/test/intl402 at main · tc39/test262 · GitHub

LangLangBart avatar Apr 10 '24 20:04 LangLangBart

@LangLangBart That's quite thorough.

sure enough..

Did the _ubrk_clone problem also occur in ICU-68232?

_ubrk_clone 69

I wonder if there's a resource that shows which ICU version shipped with each version of macos. Probably not. A quick google search turned up empty.

Others _pwritev$NOCANCEL

See also: #2214 and https://github.com/oven-sh/bun/issues/7458#issuecomment-2040990021

Because posix_spawn_file_actions_addchdir_np and posix_spawn_file_actions_addfchdir_np are just non-functional polyfills on macos 10.x to prevent bun from crashing, all the posix_spawn* functions would have to be ported from another libc implementation to gain this functionality. The two chdir functions cannot be added piecemeal because of the way posix_spawn is tightly coupled. Some node modules such as the one used by bun repl are not installed correctly as result of this issue.

kzc avatar Apr 11 '24 02:04 kzc

I wonder if there's a resource that shows which ICU version shipped with each version of macos. Probably not. A quick google search turned up empty.

Sure. Apple has a website[^1] with it.

[^1]: Apple Open Source

LangLangBart avatar Apr 11 '24 04:04 LangLangBart