Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,29 @@ jobs:
RUBY_TESTOPTS: -j${{ env.TEST_JOBS || 4 }}
timeout-minutes: 70

- run: nmake binary-package
if: ${{ matrix.test_task == 'check' }}
timeout-minutes: 10

- name: Verify binary package
shell: pwsh
if: ${{ matrix.test_task == 'check' }}
run: |
$zip = Get-Item ruby-*-mswin*.zip
$dest = Join-Path $env:RUNNER_TEMP "binpkg"
if (Test-Path $dest) { Remove-Item -Recurse -Force $dest }
New-Item -ItemType Directory $dest | Out-Null
& "$env:SystemRoot\System32\tar.exe" -x -f $zip.FullName -C $dest
if ((Get-ChildItem $dest).Count -ne 1) { throw "zip must contain a single root directory" }
$root = (Get-ChildItem $dest)[0].FullName
if (!(Test-Path "$root\bin\ruby.exe")) { throw "bin\ruby.exe not found" }
if (Get-ChildItem "$root\bin" -Filter "vcruntime140*.dll") { throw "the VC runtime must not be bundled" }
if (!(Get-ChildItem "$root\LICENSES" -ErrorAction SilentlyContinue)) { throw "LICENSES missing" }
$env:PATH = "$env:SystemRoot\System32"
& "$root\bin\ruby.exe" -v -ropenssl -rfiddle -rpsych -rzlib -e 'abort "configure_args has an absolute path: #{RbConfig::CONFIG[%q(configure_args)]}" if RbConfig::CONFIG[%q(configure_args)] =~ /\b[A-Za-z]:[\/\\]/; puts %q(binary package OK)'
if ($LASTEXITCODE -ne 0) { throw "smoke test failed" }
timeout-minutes: 5

- uses: ./.github/actions/slack
with:
label: Windows ${{ matrix.os }} / ${{ matrix.test_task || 'check' }}
Expand Down
19 changes: 19 additions & 0 deletions bootstraptest/test_fiber.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,22 @@
assert_normal_exit %q{
Thread.new { Fiber.current.kill }.join
}

# fiber_current() must not read a stale (cached) TLS execution context after a
# fiber's coroutine migrates between native threads under the M:N scheduler.
# Needs >= 2 Ractors so coroutines actually migrate; sleep drives the migration.
assert_equal 'ok', %q{
Warning[:experimental] = false
2.times.map do
Ractor.new do
200.times do
f = Fiber.new { Fiber.yield; 1 }
f.resume
sleep(0.0003) # block -> M:N native-thread migration
f.resume rescue nil
end
:ok
end
end.each(&:value)
:ok
}
4 changes: 3 additions & 1 deletion cont.c
Original file line number Diff line number Diff line change
Expand Up @@ -2187,7 +2187,9 @@ fiber_t_alloc(VALUE fiber_value, unsigned int blocking)
static inline rb_fiber_t*
fiber_current(void)
{
rb_execution_context_t *ec = GET_EC();
/* Called right after a coroutine transfer: an inlined GET_EC() may read a
* TLS pointer cached before the NT migration, so force a fresh load. */
rb_execution_context_t *ec = rb_current_ec_noinline();
return ec->fiber_ptr;
}

Expand Down
42 changes: 38 additions & 4 deletions pathname.c
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,45 @@ same_paths(VALUE self, VALUE a, VALUE b)
}

/*
* Predicate method for root directories. Returns +true+ if the
* pathname consists of consecutive slashes.
* :markup: markdown
*
* call-seq:
* root? -> true or false
*
* Returns whether the path in `self` points to a root directory.
*
* On a non-Windows system, a root directory path is one whose name begins
* with one or more slash characters (`'/'):
*
* ```ruby
* Pathname('/').root? # => true
* Pathname('////').root? # => true
* Pathname('/usr').root? # => false
* Pathname('foo').root? # => false
* ```
*
* Does not resolve dot directories:
*
* ```ruby
* Pathname('/usr/.').root? # => false
* Pathname('/usr/..').root? # => false
* ```
*
* On a Windows system, a root directory path is one whose name begins as above,
* or with a device letter followed by a colon character (`':'`)
* and one or more slash characters (`'/'):
*
* ```ruby
* Pathname('/').root? # => true
* Pathname('////').root? # => true
* Pathname('C:/').root? # => true
* Pathname('C:////').root? # => true
* Pathname('c:/').root? # => true
* Pathname('H:/').root? # => true
* Pathname('C:/m').root? # => false
* Pathname('C:').root? # => false
* ```
*
* It doesn't access the filesystem. So it may return +false+ for some
* pathnames which points to roots such as <tt>/usr/..</tt>.
*/
static VALUE
path_root_p(VALUE self)
Expand Down
84 changes: 80 additions & 4 deletions pathname_builtin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2132,13 +2132,73 @@ def readable_real?() FileTest.readable_real?(@path) end
# See <tt>FileTest.setuid?</tt>.
def setuid?() FileTest.setuid?(@path) end

# See <tt>FileTest.setgid?</tt>.
# :markup: markdown
#
# call-seq:
# setgid? -> true or false
#
# Returns whether the [setgid bit](https://en.wikipedia.org/wiki/Setuid) is set
# in the permissions for the entry at the path in `self`:
#
# ```ruby
# # Create a file and get its permissions and setgid? setting.
# pn = Pathname('doc/t.tmp')
# pn.write('foo')
# mode = pn.stat.mode.to_s(8) # => "100664"
# pn.setgid? # => false
# # Set the bit.
# pn.chmod(0o2644)
# mode = pn.stat.mode.to_s(8) # => "102644"
# pn.setgid? # => true
# pn.delete # Clean up.
# ```
#
# On Windows, the bit is never set; the method always returns `false`.
def setgid?() FileTest.setgid?(@path) end

# See <tt>FileTest.size</tt>.
# :markup: markdown
#
# call-seq:
# size -> integer
#
# Returns the size of the entry at the path in `self`:
#
# ```ruby
# Pathname('README.md').size # => 3469
# Pathname('doc').size # => 4096
# pn = Pathname('doc/t.tmp')
# pn.write('')
# pn.size # => 0
# pd.delete # Clean up.
# ```
#
# Raises an exception if the entry does not exist.
#
def size() FileTest.size(@path) end

# See <tt>FileTest.size?</tt>.
# :markup: markdown
#
# call-seq:
# size? -> integer or nil
#
# If the file or directory entry at the path in `self` exists,
# returns its size if non-zero, or `nil` if zero:
#
# ```ruby
# pn = Pathname('doc/t.tmp')
# pn.write('foo')
# pn.size? # => 3
# pn.write('')
# pn.size? # => nil
# ```
#
# Returns `nil` if the entry does not exist:
#
# ```ruby
# pn.delete
# pn.size? # => nil
# ```
#
def size?() FileTest.size?(@path) end
# See <tt>FileTest.sticky?</tt>.
def sticky?() FileTest.sticky?(@path) end
Expand Down Expand Up @@ -2296,7 +2356,23 @@ def each_entry(&block) # :yield: pathname
# Argument `permissions` is ignored on Windows.
def mkdir(...) Dir.mkdir(@path, ...) end

# See <tt>Dir.rmdir</tt>. Remove the referenced directory.
# :markup: markdown
#
# call-seq:
# rmdir -> 0
#
# Deletes the directory at the path in +self+; returns `0`:
#
# ```ruby
# pn = Pathname('doc/foo')
# pn.mkdir
# pn.rmdir
# ```
#
# Raises an exception if the directory is not empty,
# or if the path does not point to a directory.
#
# Use method #rmtree to delete the entire filetree at the path.
def rmdir() Dir.rmdir(@path) end

# :markup: markdown
Expand Down
4 changes: 3 additions & 1 deletion struct.c
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,9 @@ struct_alloc(VALUE klass)

VALUE flags = T_STRUCT;

if (n > 0 && rb_gc_size_allocatable_p(embedded_size)) {
const long embed_len_max = RSTRUCT_EMBED_LEN_MASK >> RSTRUCT_EMBED_LEN_SHIFT;

if (n > 0 && n <= embed_len_max && rb_gc_size_allocatable_p(embedded_size)) {
flags |= n << RSTRUCT_EMBED_LEN_SHIFT;
if (RCLASS_MAX_IV_COUNT(klass) == 0) {
// We set the flag before calling `NEWOBJ_OF` in case a NEWOBJ tracepoint does
Expand Down
14 changes: 10 additions & 4 deletions thread_pthread.c
Original file line number Diff line number Diff line change
Expand Up @@ -1664,10 +1664,16 @@ rb_ractor_sched_barrier_join(rb_vm_t *vm, rb_ractor_t *cr)
ractor_sched_lock(vm, cr);
{
// running_cnt
vm->ractor.sched.barrier_waiting_cnt++;
RUBY_DEBUG_LOG("waiting_cnt:%u serial:%u", vm->ractor.sched.barrier_waiting_cnt, barrier_serial);

ractor_sched_barrier_join_signal_locked(vm);
/* A not-yet-running thread (blocking/terminating, joined only to
* sync) must not be counted, or barrier_waiting_cnt > running_cnt-1. */
if (cr->threads.sched.is_running) {
vm->ractor.sched.barrier_waiting_cnt++;
RUBY_DEBUG_LOG("waiting_cnt:%u serial:%u", vm->ractor.sched.barrier_waiting_cnt, barrier_serial);
ractor_sched_barrier_join_signal_locked(vm);
}
else {
RUBY_DEBUG_LOG("join without counting (not running) serial:%u", barrier_serial);
}
ractor_sched_barrier_join_wait_locked(vm, cr->threads.sched.running);
}
ractor_sched_unlock(vm, cr);
Expand Down
89 changes: 89 additions & 0 deletions tool/binary-package.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/ruby
# Assemble a relocatable binary package from a staged installation on
# Windows (mswin). Invoked by the `binary-package` target in
# win32/Makefile.sub; not intended to be run manually.

require 'optparse'
require 'fileutils'

stage = name = srcdir = vcpkgdir = output = nil
opt = OptionParser.new
opt.on('--stage=DIR') {|v| stage = v}
opt.on('--name=NAME') {|v| name = v}
opt.on('--srcdir=DIR') {|v| srcdir = v}
opt.on('--vcpkg-dir=DIR') {|v| vcpkgdir = v}
opt.on('--output=FILE') {|v| output = v}
opt.parse!(ARGV)
abort(opt.to_s) unless stage and name and srcdir and vcpkgdir and output

stage = File.expand_path(stage)
output = File.expand_path(output)
# Normalize so backslashes are not treated as glob escapes below.
srcdir = srcdir.tr("\\", "/")
vcpkgdir = vcpkgdir.tr("\\", "/")

# Find the installed prefix inside the stage. DESTDIR staging
# reproduces the prefix below the stage directory (with the drive
# letter stripped), so descend while there is a single directory
# until bin/ appears.
root = stage
until File.directory?(File.join(root, "bin"))
entries = Dir.children(root)
unless entries.size == 1 and File.directory?(dir = File.join(root, entries[0]))
abort "#{$0}: could not locate installed prefix under #{stage}"
end
root = dir
end
abort "#{$0}: prefix must not be the stage root" if root == stage

bindir = File.join(root, "bin")

# Bundle the vcpkg runtime DLLs next to ruby.exe, where they are
# found first by the DLL search order.
dlls = Dir.glob(File.join(vcpkgdir, "bin", "*.dll"))
dlls.reject! {|f| File.basename(f, ".dll") == "readline"}
abort "#{$0}: no DLLs in #{vcpkgdir}/bin; run `nmake install-vcpkg' first" if dlls.empty?
dlls.each {|f| FileUtils.cp(f, bindir)}

# The VC runtime (vcruntime140*.dll) is deliberately not bundled.
# App-local copies are never serviced by Windows Update; the package
# assumes the VC++ Redistributable is installed.
# https://bugs.ruby-lang.org/issues/22180

# Collect license terms of everything the package redistributes.
licdir = File.join(root, "LICENSES")
FileUtils.mkdir_p(File.join(licdir, "ruby"))
%w[COPYING COPYING.ja BSDL LEGAL].each do |f|
src = File.join(srcdir, f)
FileUtils.cp(src, File.join(licdir, "ruby", f)) if File.exist?(src)
end
copyrights = Dir.glob(File.join(vcpkgdir, "share", "*", "copyright"))
abort "#{$0}: no copyright files in #{vcpkgdir}/share" if copyrights.empty?
copyrights.each do |f|
FileUtils.cp(f, File.join(licdir, "#{File.basename(File.dirname(f))}.txt"))
end

# Scrub build-machine specific paths from the recorded configure
# arguments. mkmf applies $configure_args (notably --with-opt-dir) to
# every extension build, so a leftover absolute path breaks or taints
# gem compilation on the destination machine.
rbconfigs = Dir.glob(File.join(root, "lib/ruby/*/*/rbconfig.rb"))
abort "#{$0}: rbconfig.rb not found under #{root}" if rbconfigs.empty?
rbconfigs.each do |file|
src = File.binread(file)
src.sub!(/^\s*CONFIG\["configure_args"\]\s*=\s*"\K.*(?=")/) do |args|
args.scan(/\\".*?\\"|\S+/).grep_v(%r{\b[A-Za-z]:[/\\]}).join(" ")
end or abort "#{$0}: configure_args not found in #{file}"
File.binwrite(file, src)
end

# Rename the prefix directory to the package name and archive it with
# bsdtar, which ships with Windows 10 and later. Prefer the inbox
# tar.exe; a GNU tar earlier in PATH cannot write zip with -a.
pkgdir = File.join(stage, name)
File.rename(root, pkgdir) unless root == pkgdir
FileUtils.rm_f(output)
tar = File.join(ENV["SystemRoot"] || "C:/Windows", "System32", "tar.exe")
tar = "tar" unless File.exist?(tar)
system(tar, "-a", "-c", "-f", output, "-C", stage, name, exception: true)
puts "packaged #{output}"
14 changes: 14 additions & 0 deletions win32/Makefile.sub
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,20 @@ prepare-vcpkg::
if not %%~nI == readline mklink %%~nxI %%I \
)

!ifndef VCPKG_INSTALLED
VCPKG_INSTALLED = $(srcdir)/vcpkg_installed/$(VCPKG_DEFAULT_TRIPLET)
!endif
BINARY_PACKAGE_NAME = $(RUBY_BASE_NAME)-$(RUBY_PROGRAM_VERSION)-$(arch)
BINARY_PACKAGE_STAGE = binary-package-stage

binary-package:
@if exist $(BINARY_PACKAGE_STAGE)\. rd /s /q $(BINARY_PACKAGE_STAGE)
@$(MAKE) DESTDIR="$(MAKEDIR)\$(BINARY_PACKAGE_STAGE)" install-nodoc
$(RUNRUBY) "$(tooldir)/binary-package.rb" --stage=$(BINARY_PACKAGE_STAGE) \
--name=$(BINARY_PACKAGE_NAME) --srcdir="$(srcdir)" \
--vcpkg-dir="$(VCPKG_INSTALLED)" \
--output=$(BINARY_PACKAGE_NAME).zip

.PHONY: reconfig
reconfig $(MKFILES): $(srcdir)/common.mk $(srcdir)/version.h \
$(hdrdir)/ruby/version.h $(ABI_VERSION_HDR) \
Expand Down