从机上的 Jenkins CI 插件 运行

Jenkins CI Plugin run on slave

我正在为 Jenkins CI 开发一个插件。现在当插件在从机器上 运行ning 时我遇到了问题。在奴隶上,我需要记录到主人,从主人传递参数并在奴隶上启动进程。因此我写了这段代码:

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
{
}
}

可调用的是 运行 主控插件中的这段代码:

build.getWorkspace().act(fc);

通过这样做我得到以下异常:

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.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)
  ...

我做错了什么?不幸的是,我没有找到任何使用日志和启动进程的例子。谢谢!

为了将您的 FileCallable 对象传递给远程机器,Jenkins 需要能够序列化整个对象。也就是说所有字段都需要实现java.io.Serializable.

在这种情况下,我猜测您的 SetupConfig 类型不可序列化。

如果您拥有此类型并将其标记为 Serializable,并确保其所有字段都可以序列化,那么应该可以解决此问题。如果这是第三方 class,您可能需要在 invoke 方法中构建 SetupConfig 实例。


编辑: 现在您已经发布了完整的堆栈跟踪,我们可以看到实际上 Launcher 是无法序列化的 class ( java.io.NotSerializableException: hudson.Launcher$RemoteLauncher).

您不需要将 Launcher 传递给您的可调用对象 — 您可以像刚才一样使用 ArgumentListBuilder,但您可以使用 ProcessBuilder 而不是 ProcStarter 执行 NSIS。

invoke 方法中的所有内容已经是 running on the remote machine,因此您无需执行任何特殊操作。

例如:

ArgumentListBuilder args = ...;
ProcessBuilder proc = new ProcessBuilder(args.toList());
proc.start().waitFor();