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
154 changes: 154 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
name: Release

# Binary builds: a self-contained IronRuby for each platform, made by
# Util/package-release.sh. Pushing a tag v* publishes them as a GitHub release
# (a pre-release while the version has a suffix such as -preview1);
# running the workflow by hand, or a pull request that changes the packaging, only
# keeps them as workflow artifacts.
on:
push:
tags: ['v*']
pull_request:
paths:
- .github/workflows/release.yml
- Util/package-release.sh
workflow_dispatch:

permissions:
contents: read

env:
DOTNET_NOLOGO: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
# Unlike CI, which follows prism's main branch, a release is built from the prism
# revision Src/Prism/Generated was generated from (Src/Prism/README.md), the one the
# vendored Ruby half of prism in Src/StdLib belongs to.
PRISM_REF: c53699f7f20f6b6aa4704216fbfecec897fe419a

jobs:
package:
name: ${{ matrix.rid }}
runs-on: ${{ matrix.os }}
# macOS is untested (README, Platforms): a failure there does not hold the release back.
continue-on-error: ${{ matrix.experimental }}
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-latest, rid: linux-x64, experimental: false }
- { os: windows-latest, rid: win-x64, experimental: false }
- { os: macos-latest, rid: osx-arm64, experimental: true }
defaults:
run:
shell: bash
steps:
# See ci.yml: the sources must reach the parser byte for byte.
- name: Keep checkouts byte-identical on Windows
if: runner.os == 'Windows'
run: git config --global core.autocrlf input

- uses: actions/checkout@v4
with:
path: ironruby

- uses: actions/checkout@v4
with:
repository: IronLanguages/dlr
path: dlr

- uses: actions/checkout@v4
with:
repository: ruby/prism
ref: ${{ env.PRISM_REF }}
path: prism

- name: Patch the DLR
run: |
for patch in ironruby/Build/dlr-patches/*.patch; do
echo "applying $patch"
git -C dlr apply "../$patch"
done

# The DLR multi-targets up to net9.0, so the build needs the .NET 10 SDK; the
# release itself targets net8.0.
- uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.0.x
10.0.x

- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'

# Built as in ci.yml; see there for why Windows links the sources directly.
- name: Build libprism
working-directory: prism
run: |
ruby templates/template.rb
if [ "$RUNNER_OS" = Windows ]; then
command -v gcc >/dev/null || export PATH="/c/msys64/ucrt64/bin:/c/msys64/mingw64/bin:/c/msys64/usr/bin:$PATH"
mkdir -p build
shopt -s nullglob
sources=(src/*.c src/*/*.c)
gcc -shared -O2 -std=c99 -DPRISM_EXPORT_SYMBOLS -Iinclude \
-o build/libprism.dll "${sources[@]}"
else
make shared
fi
ls -l build

# The committed loader must be the one this prism revision generates; a mismatch
# would build fine and then misread the syntax tree.
- name: Check the prism loader against the prism revision
working-directory: ironruby/Src/Prism
run: |
ruby generate.rb ../../../prism/config.yml "$RUNNER_TEMP/prism-gen"
diff -r --strip-trailing-cr Generated "$RUNNER_TEMP/prism-gen"

- name: Package
working-directory: ironruby
run: |
if [ "$GITHUB_REF_TYPE" = tag ]; then
export IR_VERSION="${GITHUB_REF_NAME#v}"
else
version=$(sed -n 's/.*DisplayVersion = "\(.*\)";.*/\1/p' Src/Ruby/CurrentVersion.cs)
export IR_VERSION="$version+$(git rev-parse --short HEAD)"
fi
Util/package-release.sh "${{ matrix.rid }}" "$GITHUB_WORKSPACE/dist"

- uses: actions/upload-artifact@v4
with:
name: ironruby-${{ matrix.rid }}
path: |
dist/*.tar.gz
dist/*.zip
if-no-files-found: error

release:
name: GitHub release
if: github.ref_type == 'tag'
needs: package
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true

- name: Checksums
working-directory: dist
run: sha256sum * | tee SHA256SUMS

- name: Publish
env:
GH_TOKEN: ${{ github.token }}
run: |
prerelease=""
case "$GITHUB_REF_NAME" in *-*) prerelease="--prerelease" ;; esac
gh release create "$GITHUB_REF_NAME" dist/* \
--repo "$GITHUB_REPOSITORY" --title "IronRuby ${GITHUB_REF_NAME#v}" \
--generate-notes $prerelease
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,28 @@ Linux is the primary target and the one the numbers above are measured on. **Win
readline are not implemented on Windows; `IO.console` is nil, so irb falls back to its ANSI
input path. macOS is untested.

## Binary releases

[Releases](https://github.com/Largo/ironruby/releases) carry a ready-to-run IronRuby for
Linux x64, Windows x64 and macOS arm64 (untested). They are self-contained, so no .NET has
to be installed. Unpack one and run the launchers in it:

```console
$ tar xzf ironruby-<version>-linux-x64.tar.gz && cd ironruby-<version>-linux-x64
$ ./ir.sh -e 'puts RUBY_DESCRIPTION'
$ ./irb.sh
$ ./igem.sh install rack
```

On Windows, unzip it and use `ir.cmd`, `irb.cmd` and `igem.cmd`. The archive has the same
layout as a checkout, with the Release build in `Src/Console/bin/Release/net8.0`, so
everything above applies to it unchanged. `irubyc` works too, but it still needs the
.NET SDK to compile the application.

`Util/package-release.sh <rid>` builds such an archive from a checkout, and
`.github/workflows/release.yml` builds all three and publishes them when a `v*` tag is
pushed.

## Building

Requires the .NET SDK, plus Ruby and a C compiler to build prism.
Expand Down Expand Up @@ -460,5 +482,5 @@ table, so such a redirection is refused with `EINVAL`), and the Unix-only half o

## License

Apache License 2.0, as the original. See `Public/License.html`.
Apache License 2.0, as the original. See [`Src/Public`](Src/Public/LICENSE.APACHE.html).
The original content description is preserved in [`README.txt`](README.txt).
21 changes: 21 additions & 0 deletions Src/StdLib/ironruby/ironruby/compiler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
require "optparse"
require "fileutils"
require "rbconfig"
require "json"
require_relative "compiler/library"

module IronRuby
Expand Down Expand Up @@ -324,11 +325,29 @@ def check_single_file_rid(rid)
end

def managed_assemblies(bin)
runtime = runtime_pack_files(bin)
Dir.glob(File.join(bin, "*.dll")).map { |f| File.basename(f, ".dll") }
.reject { |n| n == "ir" || n.start_with?("ir.") } # the console host itself
.reject { |n| runtime.include?(n + ".dll") }
.select { |n| managed_assembly?(File.join(bin, n + ".dll")) }.sort
end

# A self-contained ir (a binary release) has the whole .NET runtime next to it:
# System.Private.CoreLib, libcoreclr and ~180 more. Referencing those from the
# generated host fails to compile (CS0433, every type twice), and the app gets its
# runtime from the SDK anyway, so they are neither referenced nor copied. ir.deps.json
# lists them under the runtime pack; a framework-dependent build has no such entry.
def runtime_pack_files(bin)
deps = JSON.parse(File.read(File.join(bin, "ir.deps.json")))
deps["targets"].values.flat_map do |libraries|
libraries.select { |name, _| name.start_with?("runtimepack.") }.values
end.flat_map do |files|
(files["runtime"] || {}).keys + (files["native"] || {}).keys
end.map { |f| File.basename(f) }
rescue Errno::ENOENT, JSON::ParserError
[]
end

# A native DLL (libprism.dll from a Windows build, say) has no assembly name.
def managed_assembly?(path)
System::Reflection::AssemblyName.get_assembly_name(path)
Expand Down Expand Up @@ -519,11 +538,13 @@ def run_dotnet(options, work, name, out)
# yaml.rb) and libprism, which is a native library.
def copy_runtime(options, bin, out, skip: [])
FileUtils.mkdir_p(out)
runtime = runtime_pack_files(bin)
Dir.glob(File.join(bin, "*")).each do |src|
next unless File.file?(src)
base = File.basename(src)
next unless base =~ /\.(dll|so|dylib)\z/
next if base.start_with?("ir.")
next if runtime.include?(base)
# A referenced assembly is already in the publish output; a second copy as
# content would collide with it (NETSDK1152).
next if skip.include?(File.basename(base, ".dll"))
Expand Down
2 changes: 2 additions & 0 deletions Src/StdLib/ironruby/ironruby/compiler/library.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,10 @@ def stage_payload(payload, host_dll)
# Native libraries: only this platform's. A Linux build directory can hold a
# Windows libprism.dll as well; it would be dead weight here.
native_ext = native_extension
runtime = Compiler.runtime_pack_files(@bin)
Dir.glob(File.join(@bin, "*")).each do |src|
next unless File.file?(src) && File.extname(src) == native_ext
next if runtime.include?(File.basename(src))
next if assemblies.include?(File.basename(src, ".dll"))
next if native_ext == ".dll" && Compiler.managed_assembly?(src)
say "native: #{File.basename(src)}"
Expand Down
67 changes: 67 additions & 0 deletions Util/package-release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env bash
# Builds a binary release of IronRuby for one platform:
#
# Util/package-release.sh linux-x64 # -> dist/ironruby-<version>-linux-x64.tar.gz
# Util/package-release.sh win-x64 out # -> out/ironruby-<version>-win-x64.zip
# IR_VERSION=1.2.0-preview1 Util/package-release.sh osx-arm64
#
# The archive keeps the source tree's layout - the launchers at the top, the build in
# Src/Console/bin/Release/<tfm>, the standard library in Src/StdLib - so ir.sh/ir.cmd,
# igem, irb and irubyc work from the unpacked directory exactly as they do in a
# checkout (ir.sh picks the Release build when there is no Debug one).
#
# The build is self-contained, so it runs on a machine with no .NET installed, and
# ReadyToRun, so IronRuby's own assemblies start as native code (see publish-r2r.sh).
# irubyc still needs the .NET SDK, as it does from a checkout.
#
# It needs what a normal build needs: ../dlr with Build/dlr-patches applied, and
# ../prism/build holding the libprism for this platform. The RID must be this
# machine's: the archive carries this machine's libprism.
set -euo pipefail

RID=${1:?usage: Util/package-release.sh RID [OUTDIR]}
IR_ROOT=$(cd "$(dirname "$0")/.." && pwd)
OUT=$(mkdir -p "${2:-dist}" && cd "${2:-dist}" && pwd)
: "${IR_TFM:=net8.0}"
VERSION=${IR_VERSION:-$(sed -n 's/.*DisplayVersion = "\(.*\)";.*/\1/p' "$IR_ROOT/Src/Ruby/CurrentVersion.cs")}
NAME="ironruby-$VERSION-$RID"
STAGE="$OUT/$NAME"

rm -rf "$STAGE"
mkdir -p "$STAGE/Src"

dotnet publish "$IR_ROOT/Src/Console/Ruby.Console.csproj" \
-c Release -r "$RID" --self-contained true -f "$IR_TFM" \
-p:IronRubyTargetFrameworks="$IR_TFM" -p:PublishReadyToRun=true \
-o "$STAGE/Src/Console/bin/Release/$IR_TFM"
rm -f "$STAGE/Src/Console/bin/Release/$IR_TFM"/*.pdb

case "$RID" in
win-*) launchers=(ir.cmd irb.cmd igem.cmd iirb.cmd irubyc.cmd) ;;
*) launchers=(ir.sh irb.sh igem.sh iirb.sh irubyc.sh) ;;
esac
for f in "${launchers[@]}" README.md; do
cp "$IR_ROOT/$f" "$STAGE/"
done
cp -R "$IR_ROOT/Src/StdLib" "$STAGE/Src/"
rm -f "$STAGE/Src/StdLib/StdLib.rbproj"
cp -R "$IR_ROOT/Src/Public" "$STAGE/License"

# A quick check that the unpacked tree starts, before it is archived.
case "$RID" in
win-*) (cd "$STAGE" && cmd //c ir.cmd -e 'puts RUBY_DESCRIPTION') ;;
*) "$STAGE/ir.sh" -e 'puts RUBY_DESCRIPTION' ;;
esac

cd "$OUT"
case "$RID" in
win-*)
rm -f "$NAME.zip"
if command -v 7z >/dev/null; then 7z a -tzip -bd "$NAME.zip" "$NAME" >/dev/null
else zip -qr "$NAME.zip" "$NAME"; fi
ARCHIVE="$NAME.zip" ;;
*)
tar -czf "$NAME.tar.gz" "$NAME"
ARCHIVE="$NAME.tar.gz" ;;
esac
echo "packaged: $OUT/$ARCHIVE"
3 changes: 3 additions & 0 deletions ir.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ if "%IR_ROOT:~-1%"=="\" set "IR_ROOT=%IR_ROOT:~0,-1%"

rem IR_CONFIG=Release runs the optimized build instead. Debug stays the default,
rem exactly as in ir.sh.
rem A binary release (Util/package-release.sh) ships only the Release build: when
rem IR_CONFIG is not set and there is no Debug build, run that one.
if "%IR_CONFIG%"=="" if not exist "%IR_ROOT%\Src\Console\bin\Debug\" if exist "%IR_ROOT%\Src\Console\bin\Release\" set "IR_CONFIG=Release"
if "%IR_CONFIG%"=="" set "IR_CONFIG=Debug"

rem IR_TFM picks the target framework out of the multi-targeted build (net8.0;net10.0),
Expand Down
10 changes: 9 additions & 1 deletion ir.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,15 @@ S="$IR_ROOT/Src/StdLib"
# too - and it passes the same specs and the same IronRuby.Tests. Util/run-tests.sh and
# regen-initializers.sh read IR_CONFIG as well, so one export covers a whole session.
# Debug stays the default because every worktree and every agent script builds it.
: "${IR_CONFIG:=Debug}"
# A binary release (Util/package-release.sh) ships only the Release build: when
# IR_CONFIG is not set and there is no Debug build, run that one.
if [ -z "$IR_CONFIG" ]; then
IR_CONFIG=Debug
if [ ! -x "$IR_ROOT/Src/Console/bin/Debug/${IR_TFM:-net8.0}/ir" ] &&
[ -x "$IR_ROOT/Src/Console/bin/Release/${IR_TFM:-net8.0}/ir" ]; then
IR_CONFIG=Release
fi
fi

# IR_TFM picks the target framework, the same way IR_CONFIG picks the configuration. The
# tree multi-targets net8.0;net10.0 (Directory.Build.props), so one `dotnet build` leaves
Expand Down
Loading