Skip to content

Commit 7e10e48

Browse files
committed
Support Linuxulator on FreeBSD
1 parent b2e380f commit 7e10e48

File tree

4 files changed

+202
-34
lines changed

4 files changed

+202
-34
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 protobuf 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-*
1314
/yarn.lock

ext/sass/Rakefile

Lines changed: 158 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ end
1111
CLEAN.include %w[
1212
protoc.exe
1313
ruby
14+
true-*
1415
*.proto
1516
*.tar.gz
1617
*.zip
@@ -65,6 +66,15 @@ end
6566

6667
task 'dart-sass' do
6768
Rake::Task['dart-sass/sass'].invoke
69+
70+
if SassConfig.linuxulator?
71+
begin
72+
sh 'dart-sass/sass', '--version'
73+
rescue StandardError
74+
rm_rf 'dart-sass'
75+
raise NotImplementedError
76+
end
77+
end
6878
rescue NotImplementedError
6979
Rake::Task['node_modules/sass'].invoke
7080
end
@@ -158,6 +168,123 @@ rule '_pb.rb' => %w[.proto protoc.exe] do |t|
158168
sh './protoc.exe', '--proto_path=.', '--ruby_out=.', t.source
159169
end
160170

171+
rule(/^true-\w+$/) do |t|
172+
require_relative '../../lib/sass/elf'
173+
174+
elf = Sass.const_get(:ELF)
175+
176+
case t.name.delete_prefix('true-')
177+
when 'aarch64'
178+
ei_class = elf::ELFCLASS64
179+
ei_data = elf::ELFDATA2LSB
180+
e_machine = 0xb7
181+
e_flags = 0
182+
183+
# 0x0000000000000078: A8 0B 80 D2 mov x8, #0x5d
184+
# 0x000000000000007c: 00 00 80 D2 mov x0, #0
185+
# 0x0000000000000080: 01 00 00 D4 svc #0
186+
entry_point = ['a80b80d2000080d2010000d4'].pack('H*')
187+
when 'arm'
188+
ei_class = elf::ELFCLASS32
189+
ei_data = elf::ELFDATA2LSB
190+
e_machine = 0x28
191+
e_flags = 0x5000400
192+
193+
# 0x0000000000000054: 00 00 A0 E3 mov r0, #0
194+
# 0x0000000000000058: 01 70 A0 E3 mov r7, #1
195+
# 0x000000000000005c: 00 00 00 EF svc #0
196+
entry_point = ['0000a0e30170a0e3000000ef'].pack('H*')
197+
when 'riscv64'
198+
ei_class = elf::ELFCLASS64
199+
ei_data = elf::ELFDATA2LSB
200+
e_machine = 0xf3
201+
e_flags = 0x5
202+
203+
# 0x0000000000000078: 93 08 D0 05 addi a7, x0, 93
204+
# 0x000000000000007c: 01 45 c.li a0, 0
205+
# 0x000000000000007e: 73 00 00 00 ecall
206+
entry_point = ['9308d005014573000000'].pack('H*')
207+
when 'x86_64'
208+
ei_class = elf::ELFCLASS64
209+
ei_data = elf::ELFDATA2LSB
210+
e_machine = 0x3e
211+
e_flags = 0
212+
213+
# 0x0000000000000078: 31 FF xor edi, edi
214+
# 0x000000000000007a: B8 3C 00 00 00 mov eax, 0x3c
215+
# 0x000000000000007f: 0F 05 syscall
216+
entry_point = ['31ffb83c0000000f05'].pack('H*')
217+
when 'i386'
218+
ei_class = elf::ELFCLASS32
219+
ei_data = elf::ELFDATA2LSB
220+
e_machine = 0x03
221+
e_flags = 0
222+
223+
# 0x0000000000000054: 31 DB xor ebx, ebx
224+
# 0x0000000000000056: B8 01 00 00 00 mov eax, 1
225+
# 0x000000000000005b: CD 80 int 0x80
226+
entry_point = ['31dbb801000000cd80'].pack('H*')
227+
else
228+
raise NotImplementedError
229+
end
230+
231+
File.open(t.name, 'wb', 0o755) do |file|
232+
elf.allocate.instance_eval do
233+
case ei_class
234+
when elf::ELFCLASS32
235+
e_ehsize = elf::Elf32_Ehdr.sizeof
236+
e_phentsize = elf::Elf32_Phdr.sizeof
237+
e_shentsize = elf::Elf32_Shdr.sizeof
238+
when elf::ELFCLASS64
239+
e_ehsize = elf::Elf64_Ehdr.sizeof
240+
e_phentsize = elf::Elf64_Phdr.sizeof
241+
e_shentsize = elf::Elf64_Shdr.sizeof
242+
else
243+
raise EncodingError
244+
end
245+
e_phoff = e_ehsize
246+
p_offset = e_phoff + e_phentsize
247+
e_entry = (2**22) + p_offset
248+
p_vaddr = e_entry
249+
p_filesz = entry_point.length
250+
p_memsz = p_filesz
251+
252+
@ehdr = {
253+
e_ident: [127, 69, 76, 70, ei_class, ei_data, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
254+
e_type: 2,
255+
e_machine:,
256+
e_version: 1,
257+
e_entry:,
258+
e_phoff:,
259+
e_shoff: 0,
260+
e_flags:,
261+
e_ehsize:,
262+
e_phentsize:,
263+
e_phnum: 1,
264+
e_shentsize:,
265+
e_shnum: 0,
266+
e_shstrndx: 0
267+
}
268+
@phdrs = [
269+
{
270+
p_type: 1,
271+
p_flags: 5,
272+
p_offset:,
273+
p_vaddr:,
274+
p_paddr: 0,
275+
p_filesz:,
276+
p_memsz:,
277+
p_align: 4096
278+
}
279+
]
280+
@shdrs = []
281+
282+
dump(file)
283+
file.write(entry_point)
284+
end
285+
end
286+
end
287+
161288
# This is a FileUtils extension that defines several additional commands to be
162289
# added to the FileUtils utility functions.
163290
module FileUtils
@@ -305,6 +432,33 @@ end
305432
# The {SassConfig} module.
306433
module SassConfig
307434
module Platform
435+
CPU = case RbConfig::CONFIG['host_cpu'].downcase
436+
when /amd64|x86_64|x64/
437+
'x86_64'
438+
when /i\d86|x86|i86pc/
439+
'x86'
440+
when /arm64|aarch64/
441+
'aarch64'
442+
when /arm/
443+
'arm'
444+
when /ppc64le|powerpc64le/
445+
'powerpc64le'
446+
else
447+
RbConfig::CONFIG['host_cpu']
448+
end
449+
450+
LINUXULATOR = if RbConfig::CONFIG['host_os'].include?('freebsd')
451+
begin
452+
Rake::Task["true-#{CPU}"].invoke
453+
RbConfig::CONFIG['host_os'] = 'linux-gnu' if system("./true-#{CPU}")
454+
true
455+
rescue NotImplementedError
456+
false
457+
end
458+
else
459+
false
460+
end
461+
308462
OS = case RbConfig::CONFIG['host_os'].downcase
309463
when /darwin/
310464
'darwin'
@@ -322,21 +476,6 @@ module SassConfig
322476
RbConfig::CONFIG['host_os'].downcase
323477
end
324478

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

@@ -518,4 +657,8 @@ module SassConfig
518657
platform
519658
end
520659
end
660+
661+
def linuxulator?
662+
Platform::LINUXULATOR
663+
end
521664
end

spec/sass/elf_spec.rb

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,35 @@
77
Sass.const_get(:ELF)
88
end
99

10-
describe 'ruby program interpreter' do
11-
it 'starts with ld-' do
10+
describe 'ruby', skip: (File.exist?('/proc/self/exe') ? false : 'procfs is not available') do
11+
it 'extracts program interpreter' do
1212
expect(File.basename(described_class::INTERPRETER)).to start_with('ld-')
1313
end
14+
15+
it 'dumps elf headers' do
16+
input = StringIO.new(File.binread('/proc/self/exe'))
17+
output = StringIO.new.binmode
18+
19+
described_class.new(input).dump(output)
20+
expect(output.string).to eq(input.string.slice(0, output.length))
21+
end
1422
end
1523

16-
describe 'dart program interpreter' do
24+
describe 'dart' do
1725
subject(:interpreter) do
1826
Sass.const_get(:CLI)::INTERPRETER
1927
end
2028

21-
it 'starts with ld-' do
29+
it 'extracts program interpreter' do
2230
expect(File.basename(interpreter)).to start_with('ld-')
2331
end
2432

25-
it 'is the same as ruby' do
26-
expect(File.basename(interpreter))
27-
.to eq(File.basename(described_class::INTERPRETER))
28-
end
29-
end
30-
31-
it 'dumps elf headers' do
32-
input = StringIO.new(File.binread('/proc/self/exe'))
33-
output = StringIO.new.binmode
33+
it 'dumps elf headers' do
34+
input = StringIO.new(File.binread(Sass.const_get(:CLI)::COMMAND[0]))
35+
output = StringIO.new.binmode
3436

35-
described_class.new(input).dump(output)
36-
expect(output.string).to eq(input.string.slice(0, output.length))
37+
described_class.new(input).dump(output)
38+
expect(output.string).to eq(input.string.slice(0, output.length))
39+
end
3740
end
3841
end

0 commit comments

Comments
 (0)