Skip to content

Commit de97b49

Browse files
authored
Merge pull request #15 from savetheclocktower/candidate-2
Candidate for new `master`
2 parents 59806c3 + 49bf730 commit de97b49

File tree

8 files changed

+224
-27
lines changed

8 files changed

+224
-27
lines changed

.github/workflows/ci.yml

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,13 @@ jobs:
2121
exclude:
2222
- os: windows-latest
2323
node_version: 14
24+
- os: macos-latest
25+
node_version: 14
2426
include:
2527
- os: windows-2019
2628
node_version: 14
29+
- os: macos-13
30+
node_version: 14
2731
name: Node ${{ matrix.node_version }} on ${{ matrix.os }}
2832

2933
steps:
@@ -52,14 +56,37 @@ jobs:
5256
with:
5357
python-version: '3.10'
5458

55-
- name: Install Python setuptools
59+
- name: Install Python setuptools (Unix-likes)
5660
# This is needed for Python 3.12+, since many versions of node-gyp
5761
# are incompatible with Python 3.12+, which no-longer ships 'distutils'
5862
# out of the box. 'setuptools' package provides 'distutils'.
59-
run: python3 -m pip install setuptools
63+
if: ${{ runner.os != 'Windows' }}
64+
run: |
65+
python3 -m venv CI_venv
66+
source CI_venv/bin/activate
67+
python3 -m pip install setuptools
6068
61-
- name: Install dependencies
62-
run: npm install
69+
- name: Install Python setuptools (Windows)
70+
# This is needed for Python 3.12+, since many versions of node-gyp
71+
# are incompatible with Python 3.12+, which no-longer ships 'distutils'
72+
# out of the box. 'setuptools' package provides 'distutils'.
73+
if: ${{ runner.os == 'Windows' }}
74+
run: |
75+
python3 -m venv CI_venv
76+
CI_venv\Scripts\activate.bat
77+
python3 -m pip install setuptools
78+
79+
- name: Install dependencies (Unix-likes)
80+
if: ${{ runner.os != 'Windows' }}
81+
run: |
82+
source CI_venv/bin/activate
83+
npm install
84+
85+
- name: Install dependencies (Windows)
86+
if: ${{ runner.os == 'Windows' }}
87+
run: |
88+
CI_venv\Scripts\activate.bat
89+
npm install
6390
6491
- name: Lint
6592
run: npm run standard

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ node_modules
22
build
33
.DS_Store
44
.clang_complete
5+
ext
56

67
/browser.js
78
emsdk-portable
89
package-lock.json
10+
11+
vendor/libiconv

.npmignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
!src/bindings/*.h
99
!src/bindings/*.cc
1010

11+
!script/fetch-libiconv-61.sh
12+
1113
!vendor/libcxx/*
1214

1315
!vendor/pcre/pcre.gyp

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33

44
Native library at the core of Atom's text editor.
55

6+
## Installation notes:
7+
8+
On macOS 13 and greater, the OS no longer offers GNU `libiconv`. We handle this by downloading it from Apple’s OSS GitHub page and building it as a pre-compilation step.
9+
610
## Components:
711

812
### Patch

binding.gyp

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,40 @@
2121
"src/core",
2222
"<!(node -e \"require('nan')\")"
2323
],
24+
"conditions": [
25+
['OS=="mac"', {
26+
"postbuilds": [
27+
{
28+
'postbuild_name': 'Adjust vendored libiconv install name',
29+
'action': [
30+
'install_name_tool',
31+
"-change",
32+
"libiconv.2.dylib",
33+
"@loader_path/../../ext/lib/libiconv.2.dylib",
34+
"<(PRODUCT_DIR)/superstring.node"
35+
]
36+
37+
# NOTE: This version of the post-build action
38+
# should be used if we find it necessary to avoid
39+
# changing the `dylib`’s install name in an earlier
40+
# step.
41+
#
42+
# 'action': [
43+
# 'bash',
44+
# '<(module_root_dir)/script/adjust-install-name.sh',
45+
# '<(PRODUCT_DIR)'
46+
# ]
47+
48+
}
49+
]
50+
}]
51+
]
2452
},
2553
{
2654
"target_name": "superstring_core",
2755
"type": "static_library",
2856
"dependencies": [
29-
"./vendor/pcre/pcre.gyp:pcre",
57+
"./vendor/pcre/pcre.gyp:pcre"
3058
],
3159
"sources": [
3260
"src/core/encoding-conversion.cc",
@@ -46,8 +74,14 @@
4674
],
4775
"conditions": [
4876
['OS=="mac"', {
77+
'dependencies': [
78+
'build_libiconv'
79+
],
80+
'include_dirs': [
81+
'<(module_root_dir)/ext/include'
82+
],
4983
'link_settings': {
50-
'libraries': ['libiconv.dylib'],
84+
'libraries': ['<(module_root_dir)/ext/lib/libiconv.2.dylib']
5185
}
5286
}],
5387
['OS=="win"', {
@@ -71,6 +105,43 @@
71105
},
72106

73107
"conditions": [
108+
['OS=="mac"', {
109+
'targets+': [
110+
{
111+
"target_name": "build_libiconv",
112+
"target_type": "none",
113+
"actions": [
114+
{
115+
"action_name": "Run script",
116+
"message": "Building GNU libiconv...",
117+
"inputs": [],
118+
"outputs": ["ext"],
119+
"action": [
120+
"bash",
121+
"script/fetch-libiconv-61.sh"
122+
]
123+
}
124+
]
125+
}
126+
# {
127+
# "target_name": "find_libiconv",
128+
# "target_type": "none",
129+
# "actions": [
130+
# {
131+
# "action_name": "Run script",
132+
# "message": "Locating GNU libiconv...",
133+
# "inputs": [],
134+
# "outputs": ["vendor/libiconv/lib/libiconv.2.dylib"],
135+
# "action": [
136+
# "bash",
137+
# "script/find-gnu-libiconv.sh"
138+
# ]
139+
# }
140+
# ]
141+
# }
142+
]
143+
}],
144+
74145
# If --tests is passed to node-gyp configure, we'll build a standalone
75146
# executable that runs tests on the patch.
76147
['tests != 0', {

index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,8 @@ if (process.env.SUPERSTRING_USE_BROWSER_VERSION) {
283283
}
284284

285285
TextBuffer.prototype.baseTextMatchesFile = function (source, encoding = 'UTF8') {
286+
encoding = normalizeEncoding(encoding)
287+
286288
return new Promise((resolve, reject) => {
287289
const callback = (error, result) => {
288290
if (error) {

script/fetch-libiconv-61.sh

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
#!/bin/bash
2+
3+
# When compiling `superstring` on macOS, we used to be able to rely on the
4+
# builtin version of `libiconv`. But newer versions of macOS include FreeBSD
5+
# `libiconv`, rather than GNU `libiconv`; the two are not API-compatible.
6+
#
7+
# For this reason, we download a known good version of `libiconv` from
8+
# https://github.com/apple-oss-distributions/libiconv/tree/libiconv-61.
9+
#
10+
# We might eventually replace this approach with an explicit vendorization of
11+
# the specific files needed, but that would require a universal build of
12+
# `libiconv.2.dylib`. For now, letting the user compile their own `libiconv`
13+
# has the advantage of very likely matching the system's architecture.
14+
15+
echoerr() { echo "$@\n" >&2; }
16+
17+
create-if-missing() {
18+
if [ -z "$1" ]; then
19+
echoerr "Error: $1 is a file."
20+
usage
21+
exit 1
22+
fi
23+
if [ ! -d "$1" ]; then
24+
mkdir "$1"
25+
fi
26+
}
27+
28+
usage() {
29+
echoerr "superstring requires the GNU libiconv library, which macOS no longer bundles in recent versions. This package attempts to compile it from GitHub. If you're seeing this message, something has gone wrong; check the README for information and consider filing an issue."
30+
}
31+
32+
# Identify the directory of this script.
33+
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
34+
35+
ROOT="$SCRIPT_DIR/.."
36+
SCRATCH="$ROOT/scratch"
37+
EXT="$ROOT/ext"
38+
39+
cleanup() {
40+
if [ -d "$SCRATCH" ]; then
41+
rm -rf "$SCRATCH"
42+
fi
43+
}
44+
trap cleanup SIGINT EXIT
45+
46+
create-if-missing "$EXT"
47+
create-if-missing "$SCRATCH"
48+
49+
dylib_path="$EXT/lib/libiconv.2.dylib"
50+
51+
# If this path already exists, we'll assume libiconv has already been fetched
52+
# and compiled. Otherwise we'll do it now.
53+
if [ ! -L "$dylib_path" ]; then
54+
echo "Path $dylib_path is missing; fetching and installing libiconv."
55+
cd $SCRATCH
56+
# TODO: Instead of downloading this each time, we can check this into source
57+
# control via git subtree. That would allow someone to build this without
58+
# needing internet connectivity. But we'd still need to do a `make install` —
59+
# at least until we can produce a "universal" version of the `.dylib` and put
60+
# _that_ in source control.
61+
git clone -b libiconv-61 "https://github.com/apple-oss-distributions/libiconv.git"
62+
cd libiconv/libiconv
63+
./configure --prefix="$EXT" --libdir="$EXT/lib"
64+
make
65+
make install
66+
67+
if [ ! -L "$dylib_path" ]; then
68+
echoerr "Error: expected $dylib_path to be present, but it was not. Installation of libiconv failed. Cannot proceed."
69+
usage
70+
exit 1
71+
fi
72+
73+
# Remove the directories we don't need.
74+
rm -rf "$EXT/bin"
75+
rm -rf "$EXT/share"
76+
77+
# Copy over the license and README from the scratch directory.
78+
cp "COPYING.LIB" "$EXT"
79+
cp "README" "$EXT"
80+
else
81+
echo "Path $dylib_path is already present; skipping installation of libiconv."
82+
fi
83+
84+
cd $ROOT
85+
86+
# We expect this path to exist and be a symbolic link that points to a file.
87+
if [ ! -L "$dylib_path" ]; then
88+
echoerr "Error: expected $dylib_path to be present, but it was not. Cannot proceed."
89+
usage
90+
exit 1
91+
fi
92+
93+
# Set the install name of this library to something neutral and predictable to
94+
# make a later step easier.
95+
#
96+
# NOTE: macOS complains about this action invalidating the library's code
97+
# signature. This has not been observed to have any negative effects for
98+
# Pulsar, possibly because we sign and notarize the entire app at a later stage
99+
# of the build process. But if it _did_ have negative effects, we could switch
100+
# to a different approach and skip this step. See the `binding.gyp` file for
101+
# further details.
102+
103+
install_name_tool -id "libiconv.2.dylib" "${dylib_path}"
104+
105+
cleanup

src/bindings/text-buffer-wrapper.cc

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -387,11 +387,7 @@ static Local<Value> encode_ranges(const vector<Range> &ranges) {
387387
auto length = ranges.size() * 4;
388388
auto buffer = v8::ArrayBuffer::New(v8::Isolate::GetCurrent(), length * sizeof(uint32_t));
389389
auto result = v8::Uint32Array::New(buffer, 0, length);
390-
#if (V8_MAJOR_VERSION < 8)
391-
auto data = buffer->GetContents().Data();
392-
#else
393-
auto data = buffer->GetBackingStore()->Data();
394-
#endif
390+
auto data = buffer->GetContents().Data();
395391
memcpy(data, ranges.data(), length * sizeof(uint32_t));
396392
return result;
397393
}
@@ -615,11 +611,7 @@ void TextBufferWrapper::find_words_with_subsequence_in_range(const Nan::Function
615611
}
616612

617613
auto positions_buffer = v8::ArrayBuffer::New(v8::Isolate::GetCurrent(), positions_buffer_size);
618-
#if (V8_MAJOR_VERSION < 8)
619-
uint32_t *positions_data = reinterpret_cast<uint32_t *>(positions_buffer->GetContents().Data());
620-
#else
621-
uint32_t *positions_data = reinterpret_cast<uint32_t *>(positions_buffer->GetBackingStore()->Data());
622-
#endif
614+
uint32_t *positions_data = reinterpret_cast<uint32_t *>(positions_buffer->GetContents().Data());
623615

624616
uint32_t positions_array_index = 0;
625617
for (size_t i = 0; i < result.size() && i < max_count; i++) {
@@ -943,11 +935,7 @@ void TextBufferWrapper::load(const Nan::FunctionCallbackInfo<Value> &info) {
943935
if (!force && text_buffer.is_modified()) {
944936
Local<Value> argv[] = {Nan::Null(), Nan::Null()};
945937
auto callback = info[0].As<Function>();
946-
#if (V8_MAJOR_VERSION > 9 || (V8_MAJOR_VERSION == 9 && V8_MINOR_VERION > 4))
947-
Nan::Call(callback, callback->GetCreationContext().ToLocalChecked()->Global(), 2, argv);
948-
#else
949-
Nan::Call(callback, callback->CreationContext()->Global(), 2, argv);
950-
#endif
938+
Nan::Call(callback, callback->CreationContext()->Global(), 2, argv);
951939
return;
952940
}
953941

@@ -1051,12 +1039,7 @@ void TextBufferWrapper::base_text_matches_file(const Nan::FunctionCallbackInfo<V
10511039
bool result = std::equal(file_contents.begin(), file_contents.end(), text_buffer.base_text().begin());
10521040
Local<Value> argv[] = {Nan::Null(), Nan::New<Boolean>(result)};
10531041
auto callback = info[0].As<Function>();
1054-
1055-
#if (V8_MAJOR_VERSION > 9 || (V8_MAJOR_VERSION == 9 && V8_MINOR_VERION > 4))
1056-
Nan::Call(callback, callback->GetCreationContext().ToLocalChecked()->Global(), 2, argv);
1057-
#else
1058-
Nan::Call(callback, callback->CreationContext()->Global(), 2, argv);
1059-
#endif
1042+
Nan::Call(callback, callback->CreationContext()->Global(), 2, argv);
10601043
}
10611044
}
10621045

0 commit comments

Comments
 (0)