Skip to content

Commit 9001022

Browse files
author
R. Tyler Croy
committed
Merge pull request #16 from ysb33r/master
JRubyExec Task
2 parents 6da74a3 + 2220767 commit 9001022

File tree

11 files changed

+596
-28
lines changed

11 files changed

+596
-28
lines changed

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,3 +192,52 @@ dependencies {
192192
gems group: 'com.lookout', name: 'custom-gem', version: '1.0.+'
193193
}
194194
```
195+
196+
## JRubyExec - Task for Executing a Ruby Script
197+
198+
In a similar vein to ```JavaExec``` and ```RhinoShellExec```, the ```JRubyExec``` allows for Ruby scripts to be executed
199+
in a Gradle script using JRuby.
200+
201+
```groovy
202+
import com.lookout.jruby.JRubyExec
203+
204+
dependencies {
205+
jrubyExec 'rubygems:credit_card_validator:1.2.0'
206+
}
207+
208+
task runMyScript( type: JRubyExec ) {
209+
script 'scripts/runme.rb'
210+
scriptArgs '-x', '-y'
211+
}
212+
```
213+
214+
Common methods for ```JRubyExec``` for executing a script
215+
216+
* ```script``` - ```Object``` (Usually File or String). Path to the script.
217+
* ```scriptArgs``` - ```List```. List of arguments to pass to script.
218+
* ```workingDir``` - ```Object``` (Usually File or String). Working directory for script.
219+
* ```environment``` - ```Map```. Environment to be set. Do not set ```GEM_HOME``` or ```GEM_PATH``` with this method.
220+
* ```standardInput``` - ```InputStream```. Set an input stream to be read by the script.
221+
* ```standardOutput``` - ```OutputStream```. Capture the output of the script.
222+
* ```errorOutput``` - ```OutputStream```. Capture the error output of the script.
223+
* ```ignoreExitValue``` - ```Boolean```. Ignore the JVm exit value. Exit values are only effective if the exit value of the Ruby script is correctly communicated back to the JVM.
224+
* ```configuration``` - ```String```. Configuration to copy gems from. (*)
225+
* ```classpath``` - ```List```. Additional Jars/Directories to place on classpath.
226+
* ```jrubyVersion``` - ```String```. JRuby version to use if not the same as ```project.jruby.execVersion```.
227+
228+
(*) If ```jRubyVersion``` has not been set, ```jrubyExec``` will used as
229+
configuration. However, if ```jRubyVersion``` has been set, no gems will be used unless an explicit configuration has been provided
230+
231+
Additional ```JRubyExec``` methods for controlling the JVM instance
232+
233+
* ```jvmArgs``` - ```List```. See [jvmArgs](http://www.gradle.org/docs/current/dsl/org.gradle.api.tasks.JavaExec.html#org.gradle.api.tasks.JavaExec:jvmArgs)
234+
* ```allJvmArgs``` - ```List```. See [allJvmArgs](http://www.gradle.org/docs/current/dsl/org.gradle.api.tasks.JavaExec.html#org.gradle.api.tasks.JavaExec:allJvmArgs)
235+
* ```systemProperties``` - ```Map```. See [systemProperties](http://www.gradle.org/docs/current/dsl/org.gradle.api.tasks.JavaExec.html#org.gradle.api.tasks.JavaExec:systemProperties)
236+
* ```bootstrapClassPath``` - ```FileCollection``` or ```List```. See [bootstrapClassPath](http://www.gradle.org/docs/current/dsl/org.gradle.api.tasks.JavaExec.html#org.gradle.api.tasks.JavaExec:bootstrapClasspath)
237+
* ```minHeapSize``` - ```String```. See [minHeapSize](http://www.gradle.org/docs/current/dsl/org.gradle.api.tasks.JavaExec.html)
238+
* ```maxHeapSize``` - ```String```. See [maxHeapSize](http://www.gradle.org/docs/current/dsl/org.gradle.api.tasks.JavaExec.html#org.gradle.api.tasks.JavaExec:maxHeapSize)
239+
* ```defaultCharacterEncoding``` - ```String```. See [defaultCharacterEncoding](http://www.gradle.org/docs/current/dsl/org.gradle.api.tasks.JavaExec.html)
240+
* ```enableAssertions``` - ```Boolean```. See [enableAssertions](http://www.gradle.org/docs/current/dsl/org.gradle.api.tasks.JavaExec.html#org.gradle.api.tasks.JavaExec:enableAssertions)
241+
* ```debug``` - ```Boolean```. See [debug](http://www.gradle.org/docs/current/dsl/org.gradle.api.tasks.JavaExec.html#org.gradle.api.tasks.JavaExec:debug)
242+
* ```copyTo``` - ```JavaForkOptions```. See [copyTo](http://www.gradle.org/docs/current/dsl/org.gradle.api.tasks.JavaExec.html)
243+
* ```executable``` - ```Object``` (Usually ```File``` or ```String```). See [executable](http://www.gradle.org/docs/current/dsl/org.gradle.api.tasks.JavaExec.html#org.gradle.api.tasks.JavaExec:executable)

build.gradle

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ dependencies {
2525
compile gradleApi()
2626
compile localGroovy()
2727

28+
2829
testCompile 'junit:junit:4.+'
30+
testCompile ("org.spockframework:spock-core:0.7-groovy-${gradle.gradleVersion.startsWith('1.')?'1.8':'2.0'}") {
31+
exclude module : 'groovy-all'
32+
}
2933
}
3034

3135
test {
@@ -35,7 +39,12 @@ test {
3539
}
3640

3741
systemProperties TESTROOT : new File(buildDir,'tmp/test/unittests').absolutePath
42+
systemProperties TEST_SCRIPT_DIR : new File(projectDir,'src/test/resources/scripts').absolutePath
43+
systemProperties 'logback.configurationFile' : new File(projectDir,'src/test/resources/logback-test.xml').absolutePath
3844

45+
if(gradle.startParameter.isOffline()) {
46+
systemProperties 'TESTS_ARE_OFFLINE' : '1'
47+
}
3948
}
4049

4150

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.lookout.jruby
2+
3+
import org.gradle.api.Project
4+
import org.gradle.api.artifacts.Configuration
5+
import org.gradle.api.file.DuplicateFileCopyingException
6+
7+
/** A collection of utilities to manipulate GEMs
8+
*
9+
* @author R Tyler Croy
10+
* @author Schalk W. Cronjé
11+
*/
12+
class GemUtils {
13+
static Boolean extractGem(Project p, File gem) {
14+
def gemname = gemFullNameFromFile(gem.getName())
15+
File extract_dir = new File("./vendor/gems/$gemname")
16+
17+
if (extract_dir.exists()) {
18+
return
19+
}
20+
21+
p.exec {
22+
executable "gem"
23+
args 'install', gem, '--install-dir=./vendor', '--no-ri', '--no-rdoc'
24+
}
25+
}
26+
27+
/** Extracts a gem to a folder
28+
*
29+
* @param project Project instance
30+
* @param jRubyClasspath Where to find the jruby-complete jar (FileCollection, File or Configuration)
31+
* @param gem Gem file to extract
32+
* @param destDir Directory to extract to
33+
* @param overwrite Allow overwrite of an existing gem folder
34+
* @return
35+
*/
36+
static void extractGem(Project project, def jRubyClasspath, File gem,File destDir,boolean overwrite) {
37+
def gemname = gemFullNameFromFile(gem.name)
38+
File extract_dir = new File(destDir,gemname)
39+
40+
if (extract_dir.exists()) {
41+
if(overwrite) {
42+
project.delete extract_dir
43+
} else {
44+
throw new DuplicateFileCopyingException("Gem ${gem.name} already exists")
45+
}
46+
}
47+
48+
if( jRubyClasspath instanceof Configuration) {
49+
Set<File> cp = jRubyClasspath.files
50+
jRubyClasspath = cp.find { it.name.startsWith('jruby-complete-') }
51+
}
52+
53+
project.javaexec {
54+
setEnvironment [:]
55+
main 'org.jruby.Main'
56+
classpath jRubyClasspath
57+
args '-S', 'gem', 'install', gem, "--install-dir=${destDir.absolutePath}", '-N'
58+
}
59+
}
60+
61+
/** Take the given .gem filename (e.g. rake-10.3.2.gem) and just return the
62+
* gem "full name" (e.g. rake-10.3.2)
63+
*/
64+
static String gemFullNameFromFile(String filename) {
65+
return filename.replaceAll(~".gem", "")
66+
}
67+
}
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
package com.lookout.jruby
2+
3+
4+
import org.gradle.api.Project
5+
import org.gradle.api.artifacts.Configuration
6+
import org.gradle.api.file.FileCollection
7+
import org.gradle.api.tasks.Input
8+
import org.gradle.api.tasks.InputFile
9+
import org.gradle.api.tasks.InputFiles
10+
import org.gradle.api.tasks.JavaExec
11+
import org.gradle.api.tasks.Optional
12+
import org.gradle.api.tasks.TaskInstantiationException
13+
import org.gradle.internal.FileUtils
14+
import org.gradle.process.JavaExecSpec
15+
import org.gradle.util.CollectionUtils
16+
17+
/** Runs a ruby script using JRuby
18+
*
19+
* @author Schalk W. Cronjé
20+
*/
21+
class JRubyExec extends JavaExec {
22+
23+
static final String JRUBYEXEC_CONFIG = 'jrubyExec'
24+
25+
static void updateJRubyDependencies(Project proj) {
26+
proj.dependencies {
27+
jrubyExec "org.jruby:jruby-complete:${proj.jruby.execVersion}"
28+
}
29+
30+
proj.tasks.withType(JRubyExec) { t ->
31+
if(t.jrubyConfigurationName != proj.configurations.jrubyExec) {
32+
proj.dependencies.add(t.jrubyConfigurationName,"org.jruby:jruby-complete:${t.jrubyVersion}")
33+
}
34+
}
35+
}
36+
37+
/** Script to execute.
38+
*
39+
*/
40+
@InputFile
41+
File script
42+
43+
/** Configuration to copy gems from. If {@code jRubyVersion} has not been set, {@code jRubyExec} will used as
44+
* configuration. However, if {@code jRubyVersion} has been set, not gems will be used unless an explicit
45+
* configuration has been provided
46+
*
47+
*/
48+
@Optional
49+
@Input
50+
String configuration
51+
52+
@Input
53+
String jrubyVersion
54+
55+
JRubyExec() {
56+
super()
57+
super.setMain 'org.jruby.Main'
58+
59+
try {
60+
project.configurations.getByName(JRUBYEXEC_CONFIG)
61+
} catch(UnknownConfigurationException ) {
62+
throw new TaskInstantiationException('Cannot instantiate a JRubyExec instance before jruby plugin has been loaded')
63+
}
64+
65+
jrubyVersion = project.jruby.execVersion
66+
jrubyConfigurationName = JRUBYEXEC_CONFIG
67+
}
68+
69+
/** Sets the scriptname
70+
*
71+
* @param fName Path to script
72+
*/
73+
void setScript(Object fName) {
74+
switch (fName) {
75+
case File:
76+
script=fName
77+
break
78+
case String:
79+
script=new File(fName)
80+
break
81+
default:
82+
script=new File(fName.toString())
83+
}
84+
}
85+
86+
/** Returns a list of script arguments
87+
*/
88+
List<String> scriptArgs() {CollectionUtils.stringize(this.scriptArgs)}
89+
90+
/** Set arguments for script
91+
*
92+
* @param args
93+
*/
94+
void scriptArgs(Object... args) {
95+
this.scriptArgs.addAll(args as List)
96+
}
97+
98+
/** Returns a list of jruby arguments
99+
*/
100+
List<String> jrubyArgs() {CollectionUtils.stringize(this.jrubyArgs)}
101+
102+
/** Set arguments for jruby
103+
*
104+
* @param args
105+
*/
106+
void jrubyArgs(Object... args) {
107+
this.jrubyArgs.addAll(args as List)
108+
}
109+
110+
/** Setting the {@code jruby-complete} version allows for tasks to be run using different versions of JRuby.
111+
* This is useful for comparing the results of different version or running with a gem that is only
112+
* compatible with a specific version or when running a script with a different version that what will
113+
* be packaged.
114+
*
115+
* @param version String in the form '1.7.13'
116+
*/
117+
void setJrubyVersion(final String version) {
118+
if(version == project.jruby.execVersion) {
119+
jrubyConfigurationName = JRUBYEXEC_CONFIG
120+
} else {
121+
final String cfgName= 'jrubyExec$$' + name
122+
project.configurations.maybeCreate(cfgName)
123+
jrubyConfigurationName = cfgName
124+
}
125+
jrubyVersion = version
126+
}
127+
128+
@Override
129+
void exec() {
130+
if(configuration == null && jrubyConfigurationName == JRUBYEXEC_CONFIG) {
131+
configuration = JRUBYEXEC_CONFIG
132+
}
133+
134+
def jrubyCompletePath = project.configurations.getByName(jrubyConfigurationName)
135+
File gemDir = tmpGemDir()
136+
environment 'GEM_HOME' : gemDir
137+
138+
if(configuration != null) {
139+
project.configurations.getByName(jrubyConfigurationName)
140+
project.with {
141+
mkdir gemDir
142+
configurations.getByName(configuration).files.findAll { File f ->
143+
f.name.endsWith('.gem')
144+
}.each { File f ->
145+
GemUtils.extractGem(project,jrubyCompletePath,f,gemDir,true)
146+
}
147+
}
148+
}
149+
150+
super.classpath project.configurations.getByName(jrubyConfigurationName).files
151+
super.setArgs(getArgs())
152+
super.exec()
153+
}
154+
155+
/** getArgs gets overridden in order to add JRuby options, script name and script argumens in the correct order
156+
*/
157+
@Override
158+
List<String> getArgs() {
159+
def cmdArgs = []
160+
cmdArgs.addAll(jrubyArgs)
161+
cmdArgs.add(script.absolutePath)
162+
cmdArgs.addAll(scriptArgs)
163+
cmdArgs as List<String>
164+
}
165+
166+
@Override
167+
JavaExec setMain(final String mainClassName) {
168+
if(mainClassName == 'org.jruby.Main') {
169+
super.setMain(mainClassName)
170+
} else {
171+
throw notAllowed("Setting main class for jruby to ${mainClassName} is not a valid operation")
172+
}
173+
}
174+
175+
@Override
176+
JavaExec setArgs(Iterable<?> applicationArgs) {
177+
throw notAllowed('Use jvmArgs / scriptArgs instead')
178+
}
179+
180+
@Override
181+
JavaExec args(Object... args) {
182+
throw notAllowed('Use jvmArgs / scriptArgs instead')
183+
}
184+
185+
@Override
186+
JavaExecSpec args(Iterable<?> args) {
187+
throw notAllowed('Use jvmArgs / scriptArgs instead')
188+
}
189+
190+
/** Returns the {@code Configuration} object this task is tied to
191+
*/
192+
String getJrubyConfigurationName() {
193+
return this.jrubyConfigurationName
194+
}
195+
196+
private static UnsupportedOperationException notAllowed(final String msg) {
197+
return new UnsupportedOperationException (msg)
198+
}
199+
200+
private File tmpGemDir() {
201+
String ext = FileUtils.toSafeFileName(jrubyConfigurationName)
202+
if(configuration) {
203+
ext= "${ext}-${FileUtils.toSafeFileName(configuration)}"
204+
}
205+
new File( project.buildDir, "tmp/${ext}").absoluteFile
206+
}
207+
208+
private String jrubyConfigurationName
209+
private List<Object> jrubyArgs = []
210+
private List<Object> scriptArgs = []
211+
212+
}
213+
214+
215+

0 commit comments

Comments
 (0)