Your browser was unable to load all of the resources. They may have been blocked by your firewall, proxy or browser configuration.
Press Ctrl+F5 or Ctrl+Shift+R to have your browser try again.

Storing Groovy scripts in version control #2125

mrmrcoleman ·
Good morning,

Some of our Groovy scripts are becoming quite large so I'd like to have them in version control instead of in the interface. Ideally there would be a way to use these in the interfaces (for example in variable 'choices' generation and in build steps also) without installing Groovy on the build agents.

Is this possible?

Regards,

Mark
  • replies 10
  • views 3498
  • stars 5
robinshen ADMIN ·
This should be possible by loading the groovy script from checked out file and eval it from QB fields.
minic ·
Hi Robin,

that's an issue we are also very interested in.
(Dynamically) loading scripts from file or database and not storing them inside QB itself would be great.

Can you please provide a short example on how to load a script from file for:
- a scriptable field in QB.
- a script build step.

Perhaps a new overload "util.execute(java.io.File script, java.io.File workingDir)" or a new method ""util.runFromFile(java.io.File script, java.io.File workingDir)"?

Thanks in advance.

Regards,
Michael
robinshen ADMIN ·
Either scriptable field or script step can use Groovy Eval function to execute the script loaded from a file, for instance:

groovy:
def script = new File("some file").text;
Eval.me("logger", logger, script); //pass logger variable so that it can be called in your script. You may pass additional QB specific objects the same way if desired.
narwhal ·

What is the syntax to have multiple QuickBuild specific objects in the eval as well, such as:

logger, configuration, build, and steps all in the same eval?

narwhal ·

Alright so is there a better way than this?

groovy:

// setup a groovy shell and data bindings
def sharedData = new Binding();
def shell = new GroovyShell(sharedData);

// add classes needed for the groovy script
sharedData.setProperty('current', current);
sharedData.setProperty('system', system);
sharedData.setProperty('util', util);
sharedData.setProperty('grid', grid);
sharedData.setProperty('node', node);
sharedData.setProperty('user', user);
sharedData.setProperty('configuration', configuration);
sharedData.setProperty('build', build);
sharedData.setProperty('request', request);
sharedData.setProperty('vars', vars);
sharedData.setProperty('repositories', repositories);
sharedData.setProperty('steps', steps);
sharedData.setProperty('params', params);
sharedData.setProperty('step', step);
sharedData.setProperty('logger', logger);

// get script text
def script = new File("/home/quickbuild/scripts/script.groovy").text;

//execute script
shell.evaluate( script );

Note: script file location/name probably should pull from vars

robinshen ADMIN ·

Currently this is the way to go, please file an improvement request at track.pmease.com and we will add convenient method to make this task easier in future releases.

narwhal ·

Wouldn't it just make sense to make it a core plugin / feature like Execute Shell Command so you could do things like

  • Set current working directory
  • Add environment variables
  • add pre/post execute actions, etc

Making it a Step would be ideal.

jclx ·

We created a plugin with step that has a few fields to run scripts from a file.
This gives us a nice editor to edit files with syntax coloring and also stored them in version control.
The step has to be have a checkout step before it to get the scripts from vcs.

Feel free to use this code:
package com.pmease.quickbuild.plugin.scriptfile;

import java.io.File;
import java.util.Stack;

import org.hibernate.validator.constraints.NotEmpty;

import com.pmease.quickbuild.Context;
import com.pmease.quickbuild.Quickbuild;
import com.pmease.quickbuild.ScriptEngine;
import com.pmease.quickbuild.annotation.Editable;
import com.pmease.quickbuild.annotation.ScriptApi;
import com.pmease.quickbuild.annotation.Scriptable;
import com.pmease.quickbuild.migration.VersionedDocument;
import com.pmease.quickbuild.stepsupport.Step;
import com.pmease.quickbuild.util.FileUtils;
import com.pmease.quickbuild.util.Util;

@Editable(name = "Execute Script From File", category = "Misc", description = "Execute specified script from file")
@ScriptApi("Execute specified script from file.")
public class ScriptFileStep extends Step {
private static final long serialVersionUID = 1L;

private String scriptPath;
private String language = "groovy";

@Editable(order = 1100, name = "Script Language", description = "Specify the language the script file is written in, MVEL , Groovy, OGNL")
@NotEmpty
@ScriptApi("Get Script Language")
@Scriptable
public String getLanguage() {
	return language;
}

@Editable(order = 1200, name = "Script File", description = "Specify the path to the script file. A non-absolute path is considered to be "
		+ "relative to the workspace directory.")
@NotEmpty
@ScriptApi("Get script file path.")
@Scriptable
public String getScriptPath() {
	return scriptPath;
}

@SuppressWarnings("unused")
private void migrate1(VersionedDocument dom, Stack<Integer> versions) {
	if (versions.empty()) {
		versions.push(0);
		versions.push(0);
	}
}

@Override
public void run() {

	File buildScriptFile = FileUtils.resolvePath(Context.getConfiguration().getWorkspaceDir(), getScriptPath());

	String script = new Util().readFileAsString(buildScriptFile);

	String lng = this.getLanguage();

	if (lng != null)
	{
		if (!lng.trim().endsWith(":"))
		{
			lng = lng.trim() + ":\n";
		}

		script = lng + script;
	}

	Quickbuild.getInstance(ScriptEngine.class).evaluate(script, Context.buildEvalContext(this, null));
}

public void setLanguage(String language) {
	this.language = language;
}

public void setScriptPath(String scriptPath) {
	this.scriptPath = scriptPath;
}

}

nwoebcke ·

I am currently implementing this way of running a script in QuickBuild. However, the command

shell.evaluate("./test.groovy")

fails when the test.groovy script contains the following code:

this.class.classLoader.parseClass("./Tools.groovy")
def tools = new Tools()

the error message is:

Script2.groovy: 92: unable to resolve class Tools 
@ line 92, column 13. 
def tools = new Tools()

This only seems to be happening in QuickBuild. When I use the GroovyShell(sharedData) and related code inside another Groovy script that does the same thing as the QuickBuild step, this works as expected, that is the Tools.groovy code gets loaded into test.groovy and the 'def tools = new Tools()' line works.

Do you know why this is failing in QuickBuild? We are using version '8.0.38, 2019-02-18' at present.

robinshen ADMIN ·

QB compiles the script first and run compiled script for performance reason. So this will not work. Write test.groovy as below works around the issue:

class A {} // use a temporal class to get the classloader
def Tools = A.class.classLoader.parseClass(new File("w:\\temp\\Tools.groovy"))
def tools = Tools.newInstance()
// do things with tools