Reputation: 745
I am developing a plugin for the Jenkins CI. Now i get a problem when the plugin is running on a slave machine. On the slave I need logging to the master, passing parameters from the master and launching processes on the slave. Therefore I wrote this code:
private static class LauncherCallable implements FilePath.FileCallable<String>
{
private BuildListener listener;
private SetupConfig config;
private String nsisVersion;
private Launcher launcher;
public LauncherCallable(BuildListener listener, SetupConfig config, String nsisVersion, Launcher launcher)
{
this.listener = listener;
this.config = config;
this.nsisVersion = nsisVersion;
this.launcher = launcher;
}
@Override
public String invoke(File file, VirtualChannel vc) throws IOException, InterruptedException
{
try
{
final RemoteOutputStream ros = new RemoteOutputStream(listener.getLogger());
ScriptGenerator scriptGenerator = new ScriptGenerator(config);
listener.getLogger().println("Downloading NSIS...");
String nsisPath = Downloader.getNsisPath(listener, nsisVersion);
listener.getLogger().println("Derteming version from AssembyInfo...");
String assemblyVersion = AssemblyVersionParser.determineVersion(new File(file + "/" + config.getAssemblyInfoPath()));
listener.getLogger().println(" -> Version: " + assemblyVersion);
listener.getLogger().println("Generating .nsi file...");
String nsiString = scriptGenerator.generateScript();
nsiString = nsiString.replace("$VERSION", assemblyVersion);
listener.getLogger().println("Checking NSIS installation...");
File nsisExecutable = new File(new File(nsisPath).getAbsolutePath() + "/makensis.exe");
if (!nsisExecutable.exists())
throw new Exception("Could not find NSIS executable: " + nsisExecutable);
listener.getLogger().println("Writing .nsi file...");
String scriptGuid = UUID.randomUUID().toString();
File scriptFile = new File(file + "/" + scriptGuid + ".nsi");
PrintStream scriptOutStream = new PrintStream(new FileOutputStream(scriptFile, false));
scriptOutStream.print(nsiString);
scriptOutStream.close();
listener.getLogger().println("Launching NSIS...");
ProcStarter ps = launcher.new ProcStarter();
ArgumentListBuilder command = new ArgumentListBuilder();
command.addTokenized(nsisExecutable.toString() + " " + scriptFile.getAbsolutePath());
ps.cmds(command); //.stdout(listener)
Proc proc = launcher.launch(ps);
int nsisReturn = proc.join();
listener.getLogger().println("NSIS returned: " + nsisReturn);
listener.getLogger().println("Deleting temp file...");
if (!scriptFile.delete())
throw new Exception("Could not delete script file " + scriptFile.getAbsolutePath() + " !");
return (nsisReturn == 0) ? "SUCCESS" : "FAIL";
} catch (Exception ex)
{
ex.printStackTrace();
ex.printStackTrace(listener.getLogger());
return "FAIL";
}
}
@Override
public void checkRoles(RoleChecker rc) throws SecurityException
{
}
}
The callable is run by this code in the plugin on the master:
build.getWorkspace().act(fc);
By doing this I get the following Exception:
java.io.IOException: remote file operation failed: C:\\[...] at hudson.remoting.Channel@4cf8aa7c:CIAgent:
java.io.IOException: Unable to serialize hudson.FilePath$FileCallableWrapper@3b712782
at hudson.FilePath.act(FilePath.java:987)
at hudson.FilePath.act(FilePath.java:969)
at jenkinsnsis.JenkinsNsisPlugin.NsisPlugin.perform(NsisPlugin.java:136)
at hudson.tasks.BuildStepMonitor$1.perform(BuildStepMonitor.java:20)
at hudson.model.AbstractBuild$AbstractBuildExecution.perform(AbstractBuild.java:779)
at hudson.model.Build$BuildExecution.build(Build.java:205)
at hudson.model.Build$BuildExecution.doRun(Build.java:162)
at hudson.model.AbstractBuild$AbstractBuildExecution.run(AbstractBuild.java:537)
at hudson.model.Run.execute(Run.java:1741)
at hudson.model.FreeStyleBuild.run(FreeStyleBuild.java:43)
at hudson.model.ResourceController.execute(ResourceController.java:98)
at hudson.model.Executor.run(Executor.java:408)
Caused by: java.io.IOException: Unable to serialize hudson.FilePath$FileCallableWrapper@3b712782
at hudson.remoting.UserRequest.serialize(UserRequest.java:169)
at hudson.remoting.UserRequest.<init>(UserRequest.java:63)
at hudson.remoting.Channel.call(Channel.java:776)
at hudson.FilePath.act(FilePath.java:980)
... 11 more
Caused by: java.io.NotSerializableException: hudson.Launcher$RemoteLauncher
at java.io.ObjectOutputStream.writeObject0(Unknown Source)
at java.io.ObjectOutputStream.defaultWriteFields(Unknown Source)
...
What am I doing wrong? Unfortunately I did not find any exaples using the log and starting processes. Thank you!
Upvotes: 2
Views: 987
Reputation: 111555
In order to pass your FileCallable
object to the remote machine, Jenkins needs to be able to serialise the entire object. This means that all fields need to implement java.io.Serializable
.
In this case, I would guess that your SetupConfig
type is not serialisable.
If you own this type and can mark it as Serializable
, and ensure that all its fields can be serialised, then that should fix it. If this is a third-party class, you will likely need to construct the SetupConfig
instance within the invoke
method.
Edit: Now that you've posted the full stacktrace, we can see that in fact Launcher
is the class that cannot be serialised (java.io.NotSerializableException: hudson.Launcher$RemoteLauncher
).
You don't need to pass in Launcher
to your callable — you can use ArgumentListBuilder
as you are just now, but you can use ProcessBuilder
rather than ProcStarter
to execute NSIS.
(Everything inside the invoke
method is already running on the remote machine, so you don't need to do anything special).
For example:
ArgumentListBuilder args = ...;
ProcessBuilder proc = new ProcessBuilder(args.toList());
proc.start().waitFor();
Upvotes: 2