Skip to content

Commit 1bf1bf8

Browse files
committed
Support Linuxulator on FreeBSD
1 parent 7902be7 commit 1bf1bf8

File tree

4 files changed

+259
-38
lines changed

4 files changed

+259
-38
lines changed

.github/workflows/build.yml

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,26 @@ jobs:
8080
- os: ubuntu-latest
8181
vm:
8282
os: freebsd
83-
run: pkg install -y node npm protobuf ruby rubygem-bundler rubygem-rake
83+
run: |
84+
pkg install -y node npm protobuf ruby rubygem-bundler rubygem-rake
85+
- os: ubuntu-latest
86+
vm:
87+
os: freebsd
88+
run: |
89+
pkg install -y node npm ruby rubygem-bundler rubygem-rake
90+
sysrc linux_enable="YES"
91+
service linux start
92+
- os: ubuntu-latest
93+
vm:
94+
os: freebsd
95+
run: |
96+
pkg install -y debootstrap ruby rubygem-bundler rubygem-rake
97+
sysrc linux_enable="YES"
98+
service linux start
99+
debootstrap jammy /compat/ubuntu
100+
ln -sf ../lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 /compat/ubuntu/lib64/ld-linux-x86-64.so.2
101+
mount -t linprocfs linproc /compat/ubuntu/proc
102+
sysctl compat.linux.emul_path=/compat/ubuntu
84103
- os: ubuntu-latest
85104
vm:
86105
os: openbsd
@@ -90,11 +109,13 @@ jobs:
90109
- os: ubuntu-latest
91110
vm:
92111
os: netbsd
93-
run: /usr/sbin/pkg_add nodejs protobuf ruby
112+
run: |
113+
/usr/sbin/pkg_add nodejs protobuf ruby
94114
- os: ubuntu-latest
95115
vm:
96116
os: dragonflybsd
97-
run: pkg install -y libnghttp2 libuv node npm protobuf ruby rubygem-bundler rubygem-rake
117+
run: |
118+
pkg install -y libnghttp2 libuv node npm protobuf ruby rubygem-bundler rubygem-rake
98119
- os: ubuntu-latest
99120
vm:
100121
os: omnios
@@ -139,7 +160,7 @@ jobs:
139160
run: bundle exec rake compile
140161

141162
- name: Spec
142-
if: "!matrix.vm" # TODO: remove after https://github.com/sass/dart-sass/pull/2413
163+
if: "!matrix.vm || contains(matrix.vm.run, 'sysctl compat.linux.emul_path')" # TODO: remove after https://github.com/sass/dart-sass/pull/2413
143164
run: bundle exec rake spec
144165

145166
- name: Install

ext/sass/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@
1010
/pnpm-lock.yaml
1111
/protoc.exe
1212
/ruby/
13+
/true-*-static
1314
/yarn.lock

ext/sass/Rakefile

Lines changed: 215 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
require 'rake/clean'
44

5+
require_relative '../../lib/sass/elf'
6+
7+
ELF = Sass.const_get(:ELF)
8+
59
task default: %i[install clean]
610

711
task install: %w[cli.rb] do
@@ -11,6 +15,7 @@ end
1115
CLEAN.include %w[
1216
protoc.exe
1317
ruby
18+
true-*-static
1419
*.proto
1520
*.tar.gz
1621
*.zip
@@ -65,13 +70,20 @@ end
6570

6671
task 'dart-sass' do
6772
Rake::Task['dart-sass/sass'].invoke
73+
74+
if SassConfig.linuxulator?
75+
begin
76+
sh 'dart-sass/sass', File::NULL
77+
rescue StandardError
78+
rm_rf 'dart-sass'
79+
raise NotImplementedError
80+
end
81+
end
6882
rescue NotImplementedError
6983
Rake::Task['node_modules/sass'].invoke
7084
end
7185

7286
file 'cli.rb' => %w[dart-sass] do |t|
73-
require_relative '../../lib/sass/elf'
74-
7587
begin
7688
exe = 'dart-sass/sass'
7789
exe = "#{exe}#{['', '.bat', '.exe'].find { |ext| File.exist?("#{exe}#{ext}") }}"
@@ -89,7 +101,7 @@ file 'cli.rb' => %w[dart-sass] do |t|
89101
end
90102

91103
interpreter = File.open(command[0], 'rb') do |file|
92-
Sass.const_get(:ELF).new(file).interpreter
104+
ELF.new(file).interpreter
93105
rescue ArgumentError
94106
nil
95107
end
@@ -158,6 +170,119 @@ rule '_pb.rb' => %w[.proto protoc.exe] do |t|
158170
sh './protoc.exe', '--proto_path=.', '--ruby_out=.', t.source
159171
end
160172

173+
rule(/^true-\w+-static$/) do |t|
174+
case t.name.delete_prefix('true-').delete_suffix('-static')
175+
when 'aarch64'
176+
ei_class = ELF::ELFCLASS64
177+
ei_data = ELF::ELFDATA2LSB
178+
e_machine = 0xb7
179+
e_flags = 0
180+
181+
# 0x0000000000000078: A8 0B 80 D2 mov x8, #0x5d
182+
# 0x000000000000007c: 00 00 80 D2 mov x0, #0
183+
# 0x0000000000000080: 01 00 00 D4 svc #0
184+
entry_point = ['a80b80d2000080d2010000d4'].pack('H*')
185+
when 'arm'
186+
ei_class = ELF::ELFCLASS32
187+
ei_data = ELF::ELFDATA2LSB
188+
e_machine = 0x28
189+
e_flags = 0x5000400
190+
191+
# 0x0000000000000054: 00 00 A0 E3 mov r0, #0
192+
# 0x0000000000000058: 01 70 A0 E3 mov r7, #1
193+
# 0x000000000000005c: 00 00 00 EF svc #0
194+
entry_point = ['0000a0e30170a0e3000000ef'].pack('H*')
195+
when 'riscv64'
196+
ei_class = ELF::ELFCLASS64
197+
ei_data = ELF::ELFDATA2LSB
198+
e_machine = 0xf3
199+
e_flags = 0x5
200+
201+
# 0x0000000000000078: 93 08 D0 05 addi a7, x0, 93
202+
# 0x000000000000007c: 01 45 c.li a0, 0
203+
# 0x000000000000007e: 73 00 00 00 ecall
204+
entry_point = ['9308d005014573000000'].pack('H*')
205+
when 'x86_64'
206+
ei_class = ELF::ELFCLASS64
207+
ei_data = ELF::ELFDATA2LSB
208+
e_machine = 0x3e
209+
e_flags = 0
210+
211+
# 0x0000000000000078: 31 FF xor edi, edi
212+
# 0x000000000000007a: B8 3C 00 00 00 mov eax, 0x3c
213+
# 0x000000000000007f: 0F 05 syscall
214+
entry_point = ['31ffb83c0000000f05'].pack('H*')
215+
when 'i386'
216+
ei_class = ELF::ELFCLASS32
217+
ei_data = ELF::ELFDATA2LSB
218+
e_machine = 0x03
219+
e_flags = 0
220+
221+
# 0x0000000000000054: 31 DB xor ebx, ebx
222+
# 0x0000000000000056: B8 01 00 00 00 mov eax, 1
223+
# 0x000000000000005b: CD 80 int 0x80
224+
entry_point = ['31dbb801000000cd80'].pack('H*')
225+
else
226+
raise NotImplementedError
227+
end
228+
229+
File.open(t.name, 'wb', 0o755) do |file|
230+
ELF.allocate.instance_eval do
231+
case ei_class
232+
when ELF::ELFCLASS32
233+
e_ehsize = ELF::Elf32_Ehdr.sizeof
234+
e_phentsize = ELF::Elf32_Phdr.sizeof
235+
e_shentsize = ELF::Elf32_Shdr.sizeof
236+
when ELF::ELFCLASS64
237+
e_ehsize = ELF::Elf64_Ehdr.sizeof
238+
e_phentsize = ELF::Elf64_Phdr.sizeof
239+
e_shentsize = ELF::Elf64_Shdr.sizeof
240+
else
241+
raise EncodingError
242+
end
243+
e_phoff = e_ehsize
244+
p_offset = e_phoff + e_phentsize
245+
e_entry = (2**22) + p_offset
246+
p_vaddr = e_entry
247+
p_filesz = entry_point.length
248+
p_memsz = p_filesz
249+
250+
@ehdr = {
251+
e_ident: [127, 69, 76, 70, ei_class, ei_data, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
252+
e_type: ELF::ET_EXEC,
253+
e_machine:,
254+
e_version: 1,
255+
e_entry:,
256+
e_phoff:,
257+
e_shoff: 0,
258+
e_flags:,
259+
e_ehsize:,
260+
e_phentsize:,
261+
e_phnum: 1,
262+
e_shentsize:,
263+
e_shnum: 0,
264+
e_shstrndx: 0
265+
}
266+
@phdrs = [
267+
{
268+
p_type: ELF::PT_LOAD,
269+
p_flags: ELF::PF_R | ELF::PF_X,
270+
p_offset:,
271+
p_vaddr:,
272+
p_paddr: 0,
273+
p_filesz:,
274+
p_memsz:,
275+
p_align: 4096
276+
}
277+
]
278+
@shdrs = []
279+
280+
dump(file)
281+
end
282+
file.write(entry_point)
283+
end
284+
end
285+
161286
# This is a FileUtils extension that defines several additional commands to be
162287
# added to the FileUtils utility functions.
163288
module FileUtils
@@ -305,13 +430,95 @@ end
305430
# The {SassConfig} module.
306431
module SassConfig
307432
module Platform
433+
CPU = case RbConfig::CONFIG['host_cpu'].downcase
434+
when /amd64|x86_64|x64/
435+
'x86_64'
436+
when /i\d86|x86|i86pc/
437+
'i386'
438+
when /arm64|aarch64/
439+
'aarch64'
440+
when /arm/
441+
'arm'
442+
when /ppc64le|powerpc64le/
443+
'ppc64le'
444+
else
445+
RbConfig::CONFIG['host_cpu']
446+
end
447+
448+
LINUXULATOR = begin
449+
raise NotImplementedError unless RbConfig::CONFIG['host_os'].include?('freebsd')
450+
451+
Rake::Task["true-#{CPU}-static"].invoke
452+
453+
raise NotImplementedError unless system("./true-#{CPU}-static")
454+
455+
begin
456+
require 'fiddle'
457+
458+
lib = Fiddle.dlopen(nil)
459+
sysctlbyname = Fiddle::Function.new(
460+
lib['sysctlbyname'],
461+
[Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP, Fiddle::TYPE_SIZE_T],
462+
Fiddle::TYPE_INT
463+
)
464+
465+
name = Fiddle::Pointer.to_ptr('compat.linux.emul_path')
466+
oldp = Fiddle::NULL
467+
oldlenp = Fiddle::Pointer.malloc(Fiddle::SIZEOF_SIZE_T, Fiddle::RUBY_FREE)
468+
newp = Fiddle::NULL
469+
newlen = 0
470+
raise SystemCallError.new(nil, Fiddle.last_error) if sysctlbyname.call(name, oldp, oldlenp, newp, newlen) == -1
471+
472+
oldp = Fiddle::Pointer.malloc(oldlenp.ptr.to_i, Fiddle::RUBY_FREE)
473+
raise SystemCallError.new(nil, Fiddle.last_error) if sysctlbyname.call(name, oldp, oldlenp, newp, newlen) == -1
474+
475+
path = oldp.to_s
476+
rescue SystemCallError
477+
raise NotImplementedError
478+
end
479+
480+
RbConfig::CONFIG['host_os'] = case
481+
when (CPU == 'aarch64' &&
482+
File.exist?(File.absolute_path('lib/ld-linux-aarch64.so.1', path))) ||
483+
(CPU == 'arm' &&
484+
File.exist?(File.absolute_path('lib/ld-linux-armhf.so.3', path))) ||
485+
(CPU == 'riscv64' &&
486+
File.exist?(File.absolute_path('lib/ld-linux-riscv64-lp64d.so.1', path))) ||
487+
(CPU == 'x86_64' &&
488+
File.exist?(File.absolute_path('lib64/ld-linux-x86-64.so.2', path))) ||
489+
(CPU == 'i386' &&
490+
File.exist?(File.absolute_path('lib/ld-linux.so.2', path)))
491+
'linux-gnu'
492+
when ((CPU == 'aarch64' || CPU == 'riscv64' || CPU == 'x86_64' || CPU == 'i386') &&
493+
File.exist?(File.absolute_path("lib/ld-musl-#{CPU}.so.1", path))) ||
494+
(CPU == 'arm' &&
495+
File.exist?(File.absolute_path('lib/ld-musl-armhf.so.1', path)))
496+
'linux-musl'
497+
when ((CPU == 'aarch64' || CPU == 'riscv64' || CPU == 'x86_64') &&
498+
File.exist?(File.absolute_path('system/bin/linker64', path))) ||
499+
((CPU == 'arm' || CPU == 'i386') &&
500+
File.exist?(File.absolute_path('system/bin/linker', path)))
501+
'linux-android'
502+
when File.exist?(File.absolute_path('lib/ld-uClibc.so.0', path))
503+
'linux-uclibc'
504+
else
505+
'linux-none'
506+
end
507+
508+
true
509+
rescue NotImplementedError
510+
false
511+
end
512+
308513
OS = case RbConfig::CONFIG['host_os'].downcase
309514
when /darwin/
310515
'darwin'
311516
when /linux-android/
312517
'linux-android'
313518
when /linux-musl/
314519
'linux-musl'
520+
when /linux-none/
521+
'linux-none'
315522
when /linux-uclibc/
316523
'linux-uclibc'
317524
when /linux/
@@ -322,28 +529,17 @@ module SassConfig
322529
RbConfig::CONFIG['host_os'].downcase
323530
end
324531

325-
CPU = case RbConfig::CONFIG['host_cpu'].downcase
326-
when /amd64|x86_64|x64/
327-
'x86_64'
328-
when /i\d86|x86|i86pc/
329-
'i386'
330-
when /arm64|aarch64/
331-
'aarch64'
332-
when /arm/
333-
'arm'
334-
when /ppc64le|powerpc64le/
335-
'ppc64le'
336-
else
337-
RbConfig::CONFIG['host_cpu']
338-
end
339-
340532
ARCH = "#{CPU}-#{OS}".freeze
341533
end
342534

343535
private_constant :Platform
344536

345537
module_function
346538

539+
def linuxulator?
540+
Platform::LINUXULATOR
541+
end
542+
347543
def package_json(path = '.')
348544
require 'json'
349545

@@ -415,7 +611,7 @@ module SassConfig
415611
os = case Platform::OS
416612
when 'darwin'
417613
'osx'
418-
when 'linux', 'linux-android', 'linux-musl', 'linux-uclibc'
614+
when 'linux', 'linux-android', 'linux-musl', 'linux-none', 'linux-uclibc'
419615
'linux'
420616
when 'windows'
421617
'windows'

0 commit comments

Comments
 (0)