C.M.
C.M.

Reputation: 745

Jenkins CI Plugin run on slave

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

Answers (1)

Christopher Orr
Christopher Orr

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

Related Questions