Skip to content

Commit 2fad6cd

Browse files
authored
Merge branch 'main' into codex/rename-env-variable-and-add-cli-options
2 parents 6da1540 + 34afaa0 commit 2fad6cd

File tree

7 files changed

+226
-80
lines changed

7 files changed

+226
-80
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@
55
test/tmp/
66
.direnv/
77
pkg/
8+
9+
# Offline dependency sources
10+
.codex/deps_src/
11+
.codex/internet_resources/

MAINTANERS.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Maintainer Guide
2+
3+
This document collects notes and commands useful when maintaining
4+
`codetracer-ruby-recorder`.
5+
6+
## Development environment
7+
8+
This repository provides a Nix flake for the development shell. With
9+
`direnv` installed, the shell is loaded automatically when you enter the
10+
repository directory. Run `direnv allow` once to enable it.
11+
12+
The same environment is configured for GitHub Codespaces via the
13+
provided devcontainer configuration.
14+
15+
## Building the native extension
16+
17+
The tracer ships with a Rust extension located in `ext/native_tracer`.
18+
To build it locally run:
19+
20+
```bash
21+
just build-extension
22+
```
23+
24+
This compiles the extension in release mode using Cargo. The resulting
25+
shared library is placed under
26+
`ext/native_tracer/target/release/` and is loaded by `src/native_trace.rb`.
27+
28+
## Running tests and benchmarks
29+
30+
Execute the full test suite with:
31+
32+
```bash
33+
just test
34+
```
35+
36+
The tests run several sample programs from `test/programs` and compare
37+
the generated traces with the fixtures under `test/fixtures`.
38+
39+
Benchmarks can be executed with:
40+
41+
```bash
42+
just bench
43+
```
44+
45+
## Publishing gems
46+
47+
Two Ruby gems are published from this repository:
48+
49+
* **codetracer-ruby-recorder** – the tracer with the compiled native
50+
extension. Prebuilt gems are produced per target platform using
51+
[`rb_sys`](https://github.com/oxidize-rb/rb-sys).
52+
* **codetracer_pure_ruby_recorder** – a pure Ruby fallback without the
53+
native extension.
54+
55+
A helper script is available to build and push all gems in one go:
56+
57+
```bash
58+
ruby scripts/publish_gems.rb
59+
```
60+
61+
### Native extension gem
62+
63+
1. Install the development dependencies:
64+
65+
```bash
66+
bundle install
67+
```
68+
69+
2. For each target platform set `RB_SYS_CARGO_TARGET` and build the gem:
70+
71+
```bash
72+
RB_SYS_CARGO_TARGET=x86_64-unknown-linux-gnu rake cross_native_gem
73+
```
74+
75+
Replace the target triple with the desired platform (for example
76+
`aarch64-apple-darwin`).
77+
78+
3. Push the generated gem found in `pkg/` to RubyGems:
79+
80+
```bash
81+
gem push pkg/codetracer-ruby-recorder-<version>-x86_64-linux.gem
82+
```
83+
84+
Repeat these steps for each supported platform.
85+
86+
### Pure Ruby gem
87+
88+
The pure Ruby tracer is packaged from the files under `src/`. Build and
89+
publish it with:
90+
91+
```bash
92+
gem build codetracer_pure_ruby_recorder.gemspec
93+
gem push codetracer_pure_ruby_recorder-<version>.gem
94+
```
95+
96+
Ensure the version matches the native extension gem so that both
97+
packages can be used interchangeably.
98+
99+
All the above steps are automated by `scripts/publish_gems.rb` which
100+
builds and publishes the pure Ruby gem and all native variants.

README.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,6 @@ ruby src/native_trace.rb [--out-dir DIR] <path to ruby file>
2828

2929
however you probably want to use it in combination with CodeTracer, which would be released soon.
3030

31-
### Development
32-
33-
This repository provides a Nix flake for the development shell. With direnv installed, the shell is loaded automatically when you enter the directory. Run `direnv allow` once to enable it.
34-
35-
The same environment is configured for GitHub Codespaces via devcontainer configuration.
36-
3731
### env variables
3832

3933
* if you pass `CODETRACER_RUBY_TRACER_DEBUG=1`, you enables some additional debug-related logging
@@ -82,6 +76,8 @@ We'd be very happy if the community finds this useful, and if anyone wants to:
8276
* Provide feedback and discuss alternative implementation ideas: in the issue tracker, or in our [discord](https://discord.gg/qSDCAFMP).
8377
* Provide [sponsorship](https://opencollective.com/codetracer), so we can hire dedicated full-time maintainers for this project.
8478

79+
For maintainer instructions, see [MAINTANERS.md](MAINTANERS.md).
80+
8581
### Legal info
8682

8783
LICENSE: MIT

bin/codetracer-pure-ruby-recorder

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env ruby
2+
require 'rbconfig'
3+
script = File.expand_path('../src/trace.rb', __dir__)
4+
exec RbConfig.ruby, script, *ARGV
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Gem::Specification.new do |spec|
2+
spec.name = 'codetracer_pure_ruby_recorder'
3+
spec.version = '0.1.0'
4+
spec.authors = ['Metacraft Labs']
5+
spec.email = ['[email protected]']
6+
7+
spec.summary = 'CodeTracer Ruby recorder implemented purely in Ruby'
8+
spec.description = 'Ruby tracer that records execution steps using only Ruby code.'
9+
spec.license = 'MIT'
10+
spec.homepage = 'https://github.com/metacraft-labs/codetracer-ruby-recorder'
11+
12+
spec.files = Dir['src/**/*', 'bin/*', 'README.md', 'LICENSE']
13+
spec.require_paths = ['src']
14+
spec.bindir = 'bin'
15+
spec.executables = ['codetracer-pure-ruby-recorder']
16+
end

scripts/publish_gems.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
require 'fileutils'
5+
6+
TARGETS = [
7+
'x86_64-unknown-linux-gnu',
8+
'aarch64-unknown-linux-gnu',
9+
'x86_64-apple-darwin',
10+
'aarch64-apple-darwin',
11+
'x86_64-pc-windows-msvc'
12+
].freeze
13+
14+
15+
def run(cmd, env = {})
16+
command = env.map { |k, v| "#{k}=#{v}" }.join(' ')
17+
command = [command, cmd].reject(&:empty?).join(' ')
18+
puts "$ #{command}"
19+
system(env, *cmd.split(' ')) || abort("Command failed: #{command}")
20+
end
21+
22+
# Build and publish native extension gems
23+
TARGETS.each do |target|
24+
run('rake cross_native_gem', 'RB_SYS_CARGO_TARGET' => target)
25+
gem_file = Dir['pkg/codetracer-ruby-recorder-*.gem'].max_by { |f| File.mtime(f) }
26+
run("gem push #{gem_file}")
27+
FileUtils.rm_f(gem_file)
28+
end
29+
30+
# Build and publish pure Ruby gem
31+
run('gem build codetracer_pure_ruby_recorder.gemspec')
32+
pure_gem = Dir['codetracer_pure_ruby_recorder-*.gem'].max_by { |f| File.mtime(f) }
33+
run("gem push #{pure_gem}")
34+
FileUtils.rm_f(pure_gem)

src/trace.rb

Lines changed: 66 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,6 @@
66
require 'optparse'
77
require_relative 'recorder'
88

9-
options = {}
10-
parser = OptionParser.new do |opts|
11-
opts.banner = "usage: ruby trace.rb [options] <program> [args]"
12-
opts.on('-o DIR', '--out-dir DIR', 'Directory to write trace files') do |dir|
13-
options[:out_dir] = dir
14-
end
15-
opts.on('-h', '--help', 'Print this help') do
16-
puts opts
17-
exit
18-
end
19-
end
20-
parser.order!
21-
22-
program = ARGV.shift
23-
if program.nil?
24-
$stderr.puts parser
25-
exit 1
26-
end
27-
289
# Warning:
2910
# probably related to our development env:
3011
# if we hit an `incompatible library version` error, like
@@ -95,6 +76,7 @@ def initialize(record)
9576
@trace_stopped = false
9677
@record = record
9778
@ignore_list = []
79+
setup_tracepoints
9880
end
9981

10082
def stop_tracing
@@ -110,6 +92,32 @@ def ignore(path)
11092
@ignore_list << path
11193
end
11294

95+
def setup_tracepoints
96+
@calls_tracepoint = TracePoint.new(:call) do |tp|
97+
deactivate
98+
record_call(tp)
99+
activate
100+
end
101+
102+
@return_tracepoint = TracePoint.new(:return) do |tp|
103+
deactivate
104+
record_return(tp)
105+
activate
106+
end
107+
108+
@line_tracepoint = TracePoint.new(:line) do |tp|
109+
deactivate
110+
record_step(tp)
111+
activate
112+
end
113+
114+
@raise_tracepoint = TracePoint.new(:raise) do |tp|
115+
deactivate
116+
record_exception(tp)
117+
activate
118+
end
119+
end
120+
113121
def prepare_args(tp)
114122
args_after_self = tp.parameters.map do |(kind, name)|
115123
value = if tp.binding.nil? || name.nil?
@@ -242,64 +250,48 @@ def load_variables(binding)
242250

243251
$tracer = Tracer.new($codetracer_record)
244252

245-
# also possible :c_call, :b_call: for now record ruby calls (:call)
246-
# c_call: c lang
247-
# b_call: block entry
248-
# https://rubyapi.org/3.4/o/tracepoint
249-
$tracer.calls_tracepoint = TracePoint.new(:call) do |tp|
250-
$tracer.deactivate
251-
$tracer.record_call(tp)
252-
$tracer.activate
253-
end
253+
if __FILE__ == $PROGRAM_NAME
254+
options = {}
255+
parser = OptionParser.new do |opts|
256+
opts.banner = "usage: ruby trace.rb [options] <program> [args]"
257+
opts.on('-o DIR', '--out-dir DIR', 'Directory to write trace files') do |dir|
258+
options[:out_dir] = dir
259+
end
260+
opts.on('-h', '--help', 'Print this help') do
261+
puts opts
262+
exit
263+
end
264+
end
265+
parser.order!
254266

255-
$tracer.return_tracepoint = TracePoint.new(:return) do |tp|
256-
$tracer.deactivate
257-
$tracer.record_return(tp)
258-
$tracer.activate
259-
end
267+
program = ARGV.shift
268+
if program.nil?
269+
$stderr.puts parser
270+
exit 1
271+
end
260272

261-
$tracer.line_tracepoint = TracePoint.new(:line) do |tp|
262-
$tracer.deactivate
263-
$tracer.record_step(tp)
264-
$tracer.activate
265-
end
273+
$tracer.record.register_call('', 1, '<top-level>', [])
274+
$tracer.ignore('lib/ruby')
275+
$tracer.ignore('trace.rb')
276+
$tracer.ignore('recorder.rb')
277+
$tracer.ignore('<internal:')
278+
$tracer.ignore('gems/')
266279

267-
$tracer.raise_tracepoint = TracePoint.new(:raise) do |tp|
268-
$tracer.deactivate
269-
$tracer.record_exception(tp)
270280
$tracer.activate
271-
end
272-
273-
$tracer.record.register_call("", 1, "<top-level>", [])
274-
$tracer.ignore('lib/ruby')
275-
$tracer.ignore('trace.rb')
276-
$tracer.ignore('recorder.rb')
277-
$tracer.ignore('<internal:')
278-
$tracer.ignore('gems/')
281+
begin
282+
Kernel.load(program)
283+
rescue Exception => e
284+
old_puts ''
285+
old_puts '==== trace.rb error while tracing program ==='
286+
old_puts 'ERROR'
287+
old_puts e
288+
old_puts e.backtrace
289+
old_puts '====================='
290+
old_puts ''
291+
end
279292

280-
281-
trace_args = [program] + ARGV
282-
$tracer.activate
283-
begin
284-
Kernel.load(program)
285-
rescue Exception => e
286-
# important: rescue Exception,
287-
# not just rescue as we originally did
288-
# because a simple `rescue` doesn't catch some errors
289-
# like SystemExit and others
290-
# (when we call `exit` in the trace program and others)
291-
# https://stackoverflow.com/questions/5118745/is-systemexit-a-special-kind-of-exception
292-
old_puts ""
293-
old_puts "==== trace.rb error while tracing program ==="
294-
old_puts "ERROR"
295-
old_puts e
296-
old_puts e.backtrace
297-
old_puts "====================="
298-
old_puts ""
293+
$tracer.stop_tracing
294+
295+
out_dir = options[:out_dir] || ENV['CODETRACER_RUBY_RECORDER_OUT_DIR'] || Dir.pwd
296+
$tracer.record.serialize(program, out_dir)
299297
end
300-
ARGV = trace_args
301-
302-
$tracer.stop_tracing
303-
304-
out_dir = options[:out_dir] || ENV['CODETRACER_RUBY_RECORDER_OUT_DIR'] || Dir.pwd
305-
$tracer.record.serialize(program, out_dir)

0 commit comments

Comments
 (0)