guardinari 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rubocop.yml +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +114 -0
- data/Rakefile +42 -0
- data/ext/guardinari/Makefile +277 -0
- data/ext/guardinari/depend +4 -0
- data/ext/guardinari/extconf.rb +16 -0
- data/ext/guardinari/guardinari.c +2318 -0
- data/ext/guardinari/guardinari.l +245 -0
- data/ext/guardinari/scanner.h +494 -0
- data/lib/guardinari/version.rb +5 -0
- data/lib/guardinari.rb +9 -0
- data/sig/pagination_checker.rbs +4 -0
- metadata +61 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 695c5943a6900a961d484e6f85093a42a5ce1dd9c3c5922135694158eba58de4
|
|
4
|
+
data.tar.gz: fa9dbf6e5fed9ca33c5c898db23144107d8dc295750a0fd9bea86d37caf4c5ac
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c1f4601be00b785494de79248df3c3453843682e2b2128b835bfc870b011637c9e61bca692b310285804ad70dabe5b5b0523b0b6f6ed787c0d8f460b551fc96f
|
|
7
|
+
data.tar.gz: 2ef0c623f59bdaeb23e614f90bde9e66206f67b2a08a83860c7cf7b6738faa3e4907930a39401a50c01be1a68d6ba22cf4c0c78037075f60f869b39e7b864513
|
data/.rubocop.yml
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Matthew Eagar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Guardinari
|
|
2
|
+
|
|
3
|
+
Guardinari finds [Kaminari](https://github.com/kaminari/kaminari) queries containing broken pagination, that is, queries that have a `LIMIT` and `OFFSET` but no `ORDER BY`.
|
|
4
|
+
|
|
5
|
+
Kaminari mentions this in its README:
|
|
6
|
+
|
|
7
|
+
> Kaminari does not add an order to queries. To avoid surprises, you should generally include an order in paginated queries.
|
|
8
|
+
|
|
9
|
+
However, it's pretty easy in a non-trivial Rails app to wind up with a paginated query that does _not_ have an order-by clause. When this happens, the ordering of results is non-deterministic, and causes surprising behavior.
|
|
10
|
+
|
|
11
|
+
See, for example:
|
|
12
|
+
|
|
13
|
+
* https://github.com/kaminari/kaminari/issues/1040
|
|
14
|
+
* https://github.com/kaminari/kaminari/pull/1054
|
|
15
|
+
|
|
16
|
+
Guardinari implements a simple Flex-based lexer to find broken queries without brittle regular expressions that would be prone to false-positives and false-negatives.
|
|
17
|
+
|
|
18
|
+
It's written in C because it's ~70x faster than the equivalent `StringScanner`-based parser, written in pure Ruby - fast enough to run in dev, test, _and_ prod if you so desire.
|
|
19
|
+
|
|
20
|
+
> [!WARNING]
|
|
21
|
+
>
|
|
22
|
+
> Guardinari was written largely as a counter point to Aaron Patterson's [Ruby Outperforms C: Breaking the Catch-22](https://railsatscale.com/2023-08-29-ruby-outperforms-c/) and his [Rails World 2024 Keynote](https://www.youtube.com/watch?v=ZE6F3drGhA8) where he shows that pure Ruby parsers can outperform a C extension.
|
|
23
|
+
>
|
|
24
|
+
> Guardinari _massively_ outperforms the equivalent pure Ruby scanner because it eliminates back-and-forth - The C extension is called _once_, with the entire SQL statement in question; the Flex-based tokenizer performs zero allocations, processes the entire query, and returns a boolean `true`/`false` back to Ruby.
|
|
25
|
+
>
|
|
26
|
+
> This project has never run in a production environment, use with caution.
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
bundle add guardinari
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
With Guardinari installed, you can check whether a given query is broken:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
# Second argument are bound parametesr
|
|
39
|
+
Guardinari.find_broken_pagination('select * from users limit 10 offset 0', []) # => true
|
|
40
|
+
# or
|
|
41
|
+
Guardinari.find_broken_pagination('select * from users limit ? offset ?', [10, 0]) # => true
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
To make it useful, you probably want to run _all_ your queries through Guardinari; in Rails, you can do this with an `ActiveSupport::Notifications`:
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
ActiveSupport::Notifications.subscribe('sql.active_record') do |_name, _start, _finish, _id, payload|
|
|
49
|
+
# "SCHEMA" queries are created by Rails, we can trust that they are correct
|
|
50
|
+
next if payload[:name] == 'SCHEMA'
|
|
51
|
+
|
|
52
|
+
sql = payload[:sql]
|
|
53
|
+
binds = payload[:binds]
|
|
54
|
+
|
|
55
|
+
# Raises a Guardinari::BrokenPagination exception
|
|
56
|
+
# This exception contains the raw SQL, don't render it to the user in production
|
|
57
|
+
raise "Broken pagination" if Guardinari.find_broken_pagination(sql, binds)
|
|
58
|
+
|
|
59
|
+
# OR, custom handling
|
|
60
|
+
Rails.logger.error("Broken pagination: #{sql}") if Guardinari.find_broken_pagination?(sql, binds)
|
|
61
|
+
end
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## What constitutes "Broken"
|
|
65
|
+
|
|
66
|
+
A query is considered broken if:
|
|
67
|
+
|
|
68
|
+
- It is a `SELECT` query
|
|
69
|
+
- It has an explicit `LIMIT` _and_ `OFFSET`...
|
|
70
|
+
- but _not_ `LIMIT 1 OFFSET 0`*
|
|
71
|
+
- It does _not_ have an `ORDER BY`.
|
|
72
|
+
|
|
73
|
+
The `LIMIT 1 OFFSET 0` is ignored because sometimes Rails seems to generate these in certain circumstances where there is assumed to be exactly 0 or 1 result.
|
|
74
|
+
|
|
75
|
+
## Implementation
|
|
76
|
+
|
|
77
|
+
Guardinari is a C-extension, implementing a (very) simple Flex lexer that supports a very small subset of SQL - enough to prevent false positives that would occur with a pure regex-based solution.
|
|
78
|
+
|
|
79
|
+
What we want is effectively this:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
broken_pagination = query.match?(/select/) && query.match?(/limit/) && query.match?(/offset/) && !query.match?(/order by/)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
That is, `SELECT` queries with a limit/offset, but no order.
|
|
86
|
+
|
|
87
|
+
The above regex produces false positives and false negatives on _many_ types of input:
|
|
88
|
+
|
|
89
|
+
```sql
|
|
90
|
+
-- It cannot detect broken inner queries
|
|
91
|
+
select * from posts where user_id in (
|
|
92
|
+
-- this inner query is non-deterministically paginated
|
|
93
|
+
select id from users limit 10 offset 20
|
|
94
|
+
) order by created_at;
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
-- It matches inside strings
|
|
98
|
+
update posts set body = "select * from users limit 10 offset 0";
|
|
99
|
+
|
|
100
|
+
-- It cannot handle comments
|
|
101
|
+
/* limit 10 offset 0 */ select * from posts;
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
By implementing a simple lexer for the subset of SQL that we care about, these false-positives and false-negatives are avoided.
|
|
106
|
+
|
|
107
|
+
Of the _hundreds_ of SQL keywords, Guardinari's lexer is really only aware of the `select`, `limit`, `offset` `order` and `by`, while understanding how to skip comments, single- and double-quoted strings, and how to handle sub-queries.
|
|
108
|
+
|
|
109
|
+
## Benchmarks
|
|
110
|
+
|
|
111
|
+
An equivalent pure-Ruby `StringScanner`, with and without YJIT, along side benchmarks for Guardinari's C extension, running against a 500 character long SQL `SELECT` query:
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
data/Rakefile
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rake/testtask"
|
|
5
|
+
|
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
|
7
|
+
t.libs << "test"
|
|
8
|
+
t.libs << "lib"
|
|
9
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
require "rubocop/rake_task"
|
|
13
|
+
|
|
14
|
+
RuboCop::RakeTask.new
|
|
15
|
+
|
|
16
|
+
require "rake/extensiontask"
|
|
17
|
+
|
|
18
|
+
file "ext/guardinari/guardinari.c" => "ext/guardinari/guardinari.l" do |t|
|
|
19
|
+
sh "flex -o #{t.name} #{t.source}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
task compile: "ext/guardinari/guardinari.c"
|
|
23
|
+
|
|
24
|
+
task build: :compile
|
|
25
|
+
|
|
26
|
+
GEMSPEC = Gem::Specification.load("guardinari.gemspec")
|
|
27
|
+
|
|
28
|
+
Rake::ExtensionTask.new("guardinari", GEMSPEC) do |ext|
|
|
29
|
+
ext.lib_dir = "lib/guardinari"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
task default: %i[clobber compile test rubocop]
|
|
33
|
+
|
|
34
|
+
desc "Run tests with ASan"
|
|
35
|
+
task test_asan: :clobber do
|
|
36
|
+
ENV["SANITIZE"] = "1"
|
|
37
|
+
Rake::Task["compile"].invoke
|
|
38
|
+
|
|
39
|
+
asan_lib = `clang -print-file-name=libclang_rt.asan_osx_dynamic.dylib`.strip
|
|
40
|
+
env = { "DYLD_INSERT_LIBRARIES" => asan_lib }
|
|
41
|
+
system(env, "ruby", "-Ilib:test", *FileList["test/**/*_test.rb"]) || exit(1)
|
|
42
|
+
end
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
|
|
2
|
+
SHELL = /bin/sh
|
|
3
|
+
|
|
4
|
+
# V=0 quiet, V=1 verbose. other values don't work.
|
|
5
|
+
V = 0
|
|
6
|
+
V0 = $(V:0=)
|
|
7
|
+
Q1 = $(V:1=)
|
|
8
|
+
Q = $(Q1:0=@)
|
|
9
|
+
ECHO1 = $(V:1=@ :)
|
|
10
|
+
ECHO = $(ECHO1:0=@ echo)
|
|
11
|
+
NULLCMD = :
|
|
12
|
+
|
|
13
|
+
#### Start of system configuration section. ####
|
|
14
|
+
|
|
15
|
+
srcdir = .
|
|
16
|
+
topdir = /Users/matteagar/.asdf/installs/ruby/3.4.7/include/ruby-3.4.0
|
|
17
|
+
hdrdir = $(topdir)
|
|
18
|
+
arch_hdrdir = /Users/matteagar/.asdf/installs/ruby/3.4.7/include/ruby-3.4.0/arm64-darwin25
|
|
19
|
+
PATH_SEPARATOR = :
|
|
20
|
+
VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby
|
|
21
|
+
prefix = $(DESTDIR)/Users/matteagar/.asdf/installs/ruby/3.4.7
|
|
22
|
+
rubysitearchprefix = $(rubylibprefix)/$(sitearch)
|
|
23
|
+
rubyarchprefix = $(rubylibprefix)/$(arch)
|
|
24
|
+
rubylibprefix = $(libdir)/$(RUBY_BASE_NAME)
|
|
25
|
+
exec_prefix = $(prefix)
|
|
26
|
+
vendorarchhdrdir = $(vendorhdrdir)/$(sitearch)
|
|
27
|
+
sitearchhdrdir = $(sitehdrdir)/$(sitearch)
|
|
28
|
+
rubyarchhdrdir = $(rubyhdrdir)/$(arch)
|
|
29
|
+
vendorhdrdir = $(rubyhdrdir)/vendor_ruby
|
|
30
|
+
sitehdrdir = $(rubyhdrdir)/site_ruby
|
|
31
|
+
rubyhdrdir = $(includedir)/$(RUBY_VERSION_NAME)
|
|
32
|
+
vendorarchdir = $(vendorlibdir)/$(sitearch)
|
|
33
|
+
vendorlibdir = $(vendordir)/$(ruby_version)
|
|
34
|
+
vendordir = $(rubylibprefix)/vendor_ruby
|
|
35
|
+
sitearchdir = $(sitelibdir)/$(sitearch)
|
|
36
|
+
sitelibdir = $(sitedir)/$(ruby_version)
|
|
37
|
+
sitedir = $(rubylibprefix)/site_ruby
|
|
38
|
+
rubyarchdir = $(rubylibdir)/$(arch)
|
|
39
|
+
rubylibdir = $(rubylibprefix)/$(ruby_version)
|
|
40
|
+
sitearchincludedir = $(includedir)/$(sitearch)
|
|
41
|
+
archincludedir = $(includedir)/$(arch)
|
|
42
|
+
sitearchlibdir = $(libdir)/$(sitearch)
|
|
43
|
+
archlibdir = $(libdir)/$(arch)
|
|
44
|
+
ridir = $(datarootdir)/$(RI_BASE_NAME)
|
|
45
|
+
modular_gc_dir = $(DESTDIR)
|
|
46
|
+
mandir = $(datarootdir)/man
|
|
47
|
+
localedir = $(datarootdir)/locale
|
|
48
|
+
libdir = $(exec_prefix)/lib
|
|
49
|
+
psdir = $(docdir)
|
|
50
|
+
pdfdir = $(docdir)
|
|
51
|
+
dvidir = $(docdir)
|
|
52
|
+
htmldir = $(docdir)
|
|
53
|
+
infodir = $(datarootdir)/info
|
|
54
|
+
docdir = $(datarootdir)/doc/$(PACKAGE)
|
|
55
|
+
oldincludedir = $(DESTDIR)/usr/include
|
|
56
|
+
includedir = $(SDKROOT)$(prefix)/include
|
|
57
|
+
runstatedir = $(localstatedir)/run
|
|
58
|
+
localstatedir = $(prefix)/var
|
|
59
|
+
sharedstatedir = $(prefix)/com
|
|
60
|
+
sysconfdir = $(prefix)/etc
|
|
61
|
+
datadir = $(datarootdir)
|
|
62
|
+
datarootdir = $(prefix)/share
|
|
63
|
+
libexecdir = $(exec_prefix)/libexec
|
|
64
|
+
sbindir = $(exec_prefix)/sbin
|
|
65
|
+
bindir = $(exec_prefix)/bin
|
|
66
|
+
archdir = $(rubyarchdir)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
CC_WRAPPER =
|
|
70
|
+
CC = clang
|
|
71
|
+
CXX = clang++
|
|
72
|
+
LIBRUBY = $(LIBRUBY_SO)
|
|
73
|
+
LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a
|
|
74
|
+
LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME)
|
|
75
|
+
LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static -framework CoreFoundation $(MAINLIBS)
|
|
76
|
+
empty =
|
|
77
|
+
OUTFLAG = -o $(empty)
|
|
78
|
+
COUTFLAG = -o $(empty)
|
|
79
|
+
CSRCFLAG = $(empty)
|
|
80
|
+
|
|
81
|
+
RUBY_EXTCONF_H =
|
|
82
|
+
cflags = $(hardenflags) -fdeclspec $(optflags) $(debugflags) $(warnflags)
|
|
83
|
+
cxxflags =
|
|
84
|
+
optflags = -O3 -fno-fast-math
|
|
85
|
+
debugflags = -ggdb3
|
|
86
|
+
warnflags = -Wall -Wextra -Wextra-tokens -Wdeprecated-declarations -Wdivision-by-zero -Wdiv-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wold-style-definition -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wmisleading-indentation -Wundef
|
|
87
|
+
cppflags =
|
|
88
|
+
CCDLFLAGS = -fno-common
|
|
89
|
+
CFLAGS = $(CCDLFLAGS) $(cflags) -fno-common -pipe -fvisibility=hidden $(ARCH_FLAG)
|
|
90
|
+
INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir)
|
|
91
|
+
DEFS =
|
|
92
|
+
CPPFLAGS = -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT $(DEFS) $(cppflags)
|
|
93
|
+
CXXFLAGS = $(CCDLFLAGS) -fdeclspec $(ARCH_FLAG)
|
|
94
|
+
ldflags = -L. -fstack-protector-strong -L/opt/homebrew/Cellar/gmp/6.3.0/lib
|
|
95
|
+
dldflags = -L/opt/homebrew/Cellar/gmp/6.3.0/lib -Wl,-undefined,dynamic_lookup
|
|
96
|
+
ARCH_FLAG = -arch arm64
|
|
97
|
+
DLDFLAGS = $(ldflags) $(dldflags) $(ARCH_FLAG)
|
|
98
|
+
LDSHARED = $(CC) -dynamic -bundle
|
|
99
|
+
LDSHAREDXX = $(CXX) -dynamic -bundle
|
|
100
|
+
POSTLINK = dsymutil $@ 2>/dev/null; { test -z '$(RUBY_CODESIGN)' || codesign -s '$(RUBY_CODESIGN)' $@; }
|
|
101
|
+
AR = ar
|
|
102
|
+
LD = ld
|
|
103
|
+
EXEEXT =
|
|
104
|
+
|
|
105
|
+
RUBY_INSTALL_NAME = $(RUBY_BASE_NAME)
|
|
106
|
+
RUBY_SO_NAME = ruby.3.4
|
|
107
|
+
RUBYW_INSTALL_NAME =
|
|
108
|
+
RUBY_VERSION_NAME = $(RUBY_BASE_NAME)-$(ruby_version)
|
|
109
|
+
RUBYW_BASE_NAME = rubyw
|
|
110
|
+
RUBY_BASE_NAME = ruby
|
|
111
|
+
|
|
112
|
+
arch = arm64-darwin25
|
|
113
|
+
sitearch = $(arch)
|
|
114
|
+
ruby_version = 3.4.0
|
|
115
|
+
ruby = $(bindir)/$(RUBY_BASE_NAME)
|
|
116
|
+
RUBY = $(ruby)
|
|
117
|
+
BUILTRUBY = $(bindir)/$(RUBY_BASE_NAME)
|
|
118
|
+
ruby_headers = $(hdrdir)/ruby.h $(hdrdir)/ruby/backward.h $(hdrdir)/ruby/ruby.h $(hdrdir)/ruby/defines.h $(hdrdir)/ruby/missing.h $(hdrdir)/ruby/intern.h $(hdrdir)/ruby/st.h $(hdrdir)/ruby/subst.h $(arch_hdrdir)/ruby/config.h
|
|
119
|
+
|
|
120
|
+
RM = rm -f
|
|
121
|
+
RM_RF = rm -fr
|
|
122
|
+
RMDIRS = rmdir -p
|
|
123
|
+
MAKEDIRS = /opt/homebrew/bin/gmkdir -p
|
|
124
|
+
INSTALL = /opt/homebrew/bin/ginstall -c
|
|
125
|
+
INSTALL_PROG = $(INSTALL) -m 0755
|
|
126
|
+
INSTALL_DATA = $(INSTALL) -m 644
|
|
127
|
+
COPY = cp
|
|
128
|
+
TOUCH = exit >
|
|
129
|
+
|
|
130
|
+
#### End of system configuration section. ####
|
|
131
|
+
|
|
132
|
+
preload =
|
|
133
|
+
libpath = . $(libdir)
|
|
134
|
+
LIBPATH = -L. -L$(libdir)
|
|
135
|
+
DEFFILE =
|
|
136
|
+
|
|
137
|
+
CLEANFILES = mkmf.log
|
|
138
|
+
DISTCLEANFILES =
|
|
139
|
+
DISTCLEANDIRS =
|
|
140
|
+
|
|
141
|
+
extout =
|
|
142
|
+
extout_prefix =
|
|
143
|
+
target_prefix = /guardinari
|
|
144
|
+
LOCAL_LIBS =
|
|
145
|
+
LIBS = $(LIBRUBYARG_SHARED) -lpthread
|
|
146
|
+
ORIG_SRCS = guardinari.c
|
|
147
|
+
SRCS = $(ORIG_SRCS)
|
|
148
|
+
OBJS = guardinari.o
|
|
149
|
+
HDRS = $(srcdir)/scanner.h
|
|
150
|
+
LOCAL_HDRS =
|
|
151
|
+
TARGET = guardinari
|
|
152
|
+
TARGET_NAME = guardinari
|
|
153
|
+
TARGET_ENTRY = Init_$(TARGET_NAME)
|
|
154
|
+
DLLIB = $(TARGET).bundle
|
|
155
|
+
EXTSTATIC =
|
|
156
|
+
STATIC_LIB =
|
|
157
|
+
|
|
158
|
+
TIMESTAMP_DIR = .
|
|
159
|
+
BINDIR = $(bindir)
|
|
160
|
+
RUBYCOMMONDIR = $(sitedir)$(target_prefix)
|
|
161
|
+
RUBYLIBDIR = $(sitelibdir)$(target_prefix)
|
|
162
|
+
RUBYARCHDIR = $(sitearchdir)$(target_prefix)
|
|
163
|
+
HDRDIR = $(sitehdrdir)$(target_prefix)
|
|
164
|
+
ARCHHDRDIR = $(sitearchhdrdir)$(target_prefix)
|
|
165
|
+
TARGET_SO_DIR =
|
|
166
|
+
TARGET_SO = $(TARGET_SO_DIR)$(DLLIB)
|
|
167
|
+
CLEANLIBS = $(TARGET_SO) $(TARGET_SO:=.dSYM)
|
|
168
|
+
CLEANOBJS = $(OBJS) *.bak
|
|
169
|
+
TARGET_SO_DIR_TIMESTAMP = $(TIMESTAMP_DIR)/.sitearchdir.-.guardinari.time
|
|
170
|
+
|
|
171
|
+
all: $(DLLIB)
|
|
172
|
+
static: $(STATIC_LIB)
|
|
173
|
+
.PHONY: all install static install-so install-rb
|
|
174
|
+
.PHONY: clean clean-so clean-static clean-rb
|
|
175
|
+
|
|
176
|
+
clean-static::
|
|
177
|
+
clean-rb-default::
|
|
178
|
+
clean-rb::
|
|
179
|
+
clean-so::
|
|
180
|
+
clean: clean-so clean-static clean-rb-default clean-rb
|
|
181
|
+
-$(Q)$(RM_RF) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES) .*.time
|
|
182
|
+
|
|
183
|
+
distclean-rb-default::
|
|
184
|
+
distclean-rb::
|
|
185
|
+
distclean-so::
|
|
186
|
+
distclean-static::
|
|
187
|
+
distclean: clean distclean-so distclean-static distclean-rb-default distclean-rb
|
|
188
|
+
-$(Q)$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log
|
|
189
|
+
-$(Q)$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES)
|
|
190
|
+
-$(Q)$(RMDIRS) $(DISTCLEANDIRS) 2> /dev/null || true
|
|
191
|
+
|
|
192
|
+
realclean: distclean
|
|
193
|
+
install: install-so install-rb
|
|
194
|
+
|
|
195
|
+
install-so: $(DLLIB) $(TARGET_SO_DIR_TIMESTAMP)
|
|
196
|
+
$(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR)
|
|
197
|
+
clean-static::
|
|
198
|
+
-$(Q)$(RM) $(STATIC_LIB)
|
|
199
|
+
install-rb: pre-install-rb do-install-rb install-rb-default
|
|
200
|
+
install-rb-default: pre-install-rb-default do-install-rb-default
|
|
201
|
+
pre-install-rb: Makefile
|
|
202
|
+
pre-install-rb-default: Makefile
|
|
203
|
+
do-install-rb:
|
|
204
|
+
do-install-rb-default:
|
|
205
|
+
pre-install-rb-default:
|
|
206
|
+
@$(NULLCMD)
|
|
207
|
+
$(TARGET_SO_DIR_TIMESTAMP):
|
|
208
|
+
$(Q) $(MAKEDIRS) $(@D) $(RUBYARCHDIR)
|
|
209
|
+
$(Q) $(TOUCH) $@
|
|
210
|
+
|
|
211
|
+
site-install: site-install-so site-install-rb
|
|
212
|
+
site-install-so: install-so
|
|
213
|
+
site-install-rb: install-rb
|
|
214
|
+
|
|
215
|
+
.SUFFIXES: .c .m .cc .mm .cxx .cpp .o .S
|
|
216
|
+
|
|
217
|
+
.cc.o:
|
|
218
|
+
$(ECHO) compiling $(<)
|
|
219
|
+
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
|
|
220
|
+
|
|
221
|
+
.cc.S:
|
|
222
|
+
$(ECHO) translating $(<)
|
|
223
|
+
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
|
|
224
|
+
|
|
225
|
+
.mm.o:
|
|
226
|
+
$(ECHO) compiling $(<)
|
|
227
|
+
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
|
|
228
|
+
|
|
229
|
+
.mm.S:
|
|
230
|
+
$(ECHO) translating $(<)
|
|
231
|
+
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
|
|
232
|
+
|
|
233
|
+
.cxx.o:
|
|
234
|
+
$(ECHO) compiling $(<)
|
|
235
|
+
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
|
|
236
|
+
|
|
237
|
+
.cxx.S:
|
|
238
|
+
$(ECHO) translating $(<)
|
|
239
|
+
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
|
|
240
|
+
|
|
241
|
+
.cpp.o:
|
|
242
|
+
$(ECHO) compiling $(<)
|
|
243
|
+
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
|
|
244
|
+
|
|
245
|
+
.cpp.S:
|
|
246
|
+
$(ECHO) translating $(<)
|
|
247
|
+
$(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
|
|
248
|
+
|
|
249
|
+
.c.o:
|
|
250
|
+
$(ECHO) compiling $(<)
|
|
251
|
+
$(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
|
|
252
|
+
|
|
253
|
+
.c.S:
|
|
254
|
+
$(ECHO) translating $(<)
|
|
255
|
+
$(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
|
|
256
|
+
|
|
257
|
+
.m.o:
|
|
258
|
+
$(ECHO) compiling $(<)
|
|
259
|
+
$(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
|
|
260
|
+
|
|
261
|
+
.m.S:
|
|
262
|
+
$(ECHO) translating $(<)
|
|
263
|
+
$(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
|
|
264
|
+
|
|
265
|
+
$(TARGET_SO): $(OBJS) Makefile
|
|
266
|
+
$(ECHO) linking shared-object guardinari/$(DLLIB)
|
|
267
|
+
-$(Q)$(RM) $(@)
|
|
268
|
+
$(Q) $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS)
|
|
269
|
+
$(Q) $(POSTLINK)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
###
|
|
274
|
+
all: guardinari.c
|
|
275
|
+
|
|
276
|
+
guardinari.c: guardinari.l
|
|
277
|
+
flex -o guardinari.c guardinari.l
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "mkmf"
|
|
4
|
+
|
|
5
|
+
# Makes all symbols private by default to avoid unintended conflict
|
|
6
|
+
# with other gems. To explicitly export symbols you can use RUBY_FUNC_EXPORTED
|
|
7
|
+
# selectively, or entirely remove this flag.
|
|
8
|
+
append_cflags("-fvisibility=hidden")
|
|
9
|
+
append_cflags("-O3")
|
|
10
|
+
|
|
11
|
+
if ENV["SANITIZE"]
|
|
12
|
+
$CFLAGS << " -fsanitize=address,undefined -fno-omit-frame-pointer" # rubocop:disable Style/GlobalVars
|
|
13
|
+
$LDFLAGS << " -fsanitize=address,undefined" # rubocop:disable Style/GlobalVars
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
create_makefile("guardinari/guardinari")
|