如何让 Jenkins 使用来自 COMPOSER_HOME/bin 的二进制文件?

How to get Jenkins working with binaries from COMPOSER_HOME/bin?

我正在做一个项目,想为它使用 Jenkins。配置应该通过一个简单的 build.xml 完成,目标是:

<?xml version="1.0" encoding="UTF-8"?>
<project name="MyProject" default="full-build">
    <property name="phpcs"   value="phpcs"/>
    <property name="phpdox"  value="phpdox"/>
    <target name="full-build" depends="lint, phpdox" description="Performs static analysis, runs the tests, and generates project documentation"/>
    <target name="lint" description="Perform syntax check of sourcecode files">
        <apply executable="php" taskname="lint" failonerror="true">
            <arg value="-l" />
            <fileset dir="${basedir}/module/">
                <include name="**/*.php" />
            </fileset>
            <fileset dir="${basedir}/phpunit/tests">
                <include name="**/*.php" />
            </fileset>
        </apply>
    </target>
    <target name="phpdox" description="Generate project documentation using phpDox">
        <exec executable="${phpdox}" dir="${basedir}/ci/phpdox" taskname="phpdox"/>
    </target>
</project>

PHP Lint (php -l) 工作完美运行:

控制台输出

Started by user anonymous
Building in workspace D:\Data\myproject
 > git.exe rev-parse --is-inside-work-tree # timeout=10
Fetching changes from the remote Git repository
 > git.exe config remote.origin.url https://github.com/MyAccount/MyProject.git # timeout=10
Fetching upstream changes from https://github.com/MyAccount/MyProject.git
 > git.exe --version # timeout=10
using .gitcredentials to set credentials
 > git.exe config --local credential.username myusername # timeout=10
 > git.exe config --local credential.helper store --file=\"C:\Windows\TEMP\git8298647783094567497.credentials\" # timeout=10
Setting http proxy: my.proxy.tld:8080
 > git.exe -c core.askpass=true fetch --tags --progress https://github.com/MyAccount/MyProject.git +refs/heads/*:refs/remotes/origin/*
 > git.exe config --local --remove-section credential # timeout=10
 > git.exe rev-parse "refs/remotes/origin/master^{commit}" # timeout=10
 > git.exe rev-parse "refs/remotes/origin/origin/master^{commit}" # timeout=10
Checking out Revision 54af2180160f47d518c42f58f56cba175ca2ee39 (refs/remotes/origin/master)
 > git.exe config core.sparsecheckout # timeout=10
 > git.exe checkout -f 54af2180160f47d518c42f58f56cba175ca2ee39
 > git.exe rev-list 54af2180160f47d518c42f58f56cba175ca2ee39 # timeout=10
[myproject] $ cmd.exe /C '"ant.bat && exit %%ERRORLEVEL%%"'
Buildfile: D:\Data\myproject\build.xml

lint:
     [lint] No syntax errors detected in D:\Data\myproject\module\Application\Module.php
     ...
     [lint] No syntax errors detected in D:\Data\myproject\phpunit\tests\Application\DummyTest.php

但是 PHPDox 的执行失败了:

phpdox:

BUILD FAILED
D:\Data\myproject\build.xml:18: Execute failed: java.io.IOException: Cannot run program "phpdox" (in directory "D:\Data\myproject\ci\phpdox"): CreateProcess error=2, The system cannot find the file specified
    at java.lang.ProcessBuilder.start(ProcessBuilder.java:1048)
    at java.lang.Runtime.exec(Runtime.java:620)
    at org.apache.tools.ant.taskdefs.launcher.Java13CommandLauncher.exec(Java13CommandLauncher.java:58)
    at org.apache.tools.ant.taskdefs.Execute.launch(Execute.java:428)
    at org.apache.tools.ant.taskdefs.Execute.execute(Execute.java:442)
    at org.apache.tools.ant.taskdefs.ExecTask.runExecute(ExecTask.java:629)
    at org.apache.tools.ant.taskdefs.ExecTask.runExec(ExecTask.java:670)
    at org.apache.tools.ant.taskdefs.ExecTask.execute(ExecTask.java:496)
    at org.apache.tools.ant.UnknownElement.execute(UnknownElement.java:293)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.tools.ant.dispatch.DispatchUtils.execute(DispatchUtils.java:106)
    at org.apache.tools.ant.Task.perform(Task.java:348)
    at org.apache.tools.ant.Target.execute(Target.java:435)
    at org.apache.tools.ant.Target.performTasks(Target.java:456)
    at org.apache.tools.ant.Project.executeSortedTargets(Project.java:1405)
    at org.apache.tools.ant.Project.executeTarget(Project.java:1376)
    at org.apache.tools.ant.helper.DefaultExecutor.executeTargets(DefaultExecutor.java:41)
    at org.apache.tools.ant.Project.executeTargets(Project.java:1260)
    at org.apache.tools.ant.Main.runBuild(Main.java:853)
    at org.apache.tools.ant.Main.startAnt(Main.java:235)
    at org.apache.tools.ant.launch.Launcher.run(Launcher.java:285)
    at org.apache.tools.ant.launch.Launcher.main(Launcher.java:112)
Caused by: java.io.IOException: CreateProcess error=2, The system cannot find the file specified
    at java.lang.ProcessImpl.create(Native Method)
    at java.lang.ProcessImpl.<init>(ProcessImpl.java:386)
    at java.lang.ProcessImpl.start(ProcessImpl.java:137)
    at java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
    ... 24 more

Total time: 8 seconds
Build step 'Invoke Ant' marked build as failure
Finished: FAILURE

我在其他 Composer 软件包中遇到了同样的问题,这些软件包已安装 globally 并从 COMPOSER_HOME/bin 使用(例如 PHP_Codesniffer)。

我试过用另一种方式定义命令(比如<exec executable="cmd"><arg line="/c phpdox ..." /></exec>),但它也不起作用。

直接执行 build.xmlant 是可行的。

Composer 的环境变量设置正确——我可以通过 CLI(CMD 或 GitBash/MinGW)调用这些工具。所以问题一定是由其他原因引起的。它可以是什么?

如何让 Jenkins 使用通过 Composer 全局安装的包?

Linux(在我的例子中 Ubuntu 14.04 服务器)

在 Jenkins 配置中设置环境变量(Manage Jenkins -> Configure System -> Global properties -> Environment variablesname=PATH, value=$PATH:$COMPOSER_HOME/vendor/bin/)解决了问题。

$ echo $COMPOSER_HOME
/usr/share/.composer

请注意,$COMPOSER_HOME should/may 不能设置为 (root) 用户(例如 /root/.composer)的子文件夹,因为它可能导致 :

Execute failed: java.io.IOException: Cannot run program "phpcs": error=13, Permission denied


Windows(在我的例子中 Windows Server 2008 r2)

除了 Composer 路径的设置之外,还需要一些进一步的步骤 were/are:

首先我得到了错误

Execute failed: java.io.IOException: Cannot run program "phpcs": error=2, The system cannot find file specified

在我读到的一个论坛上,目标需要获取可执行文件的路径,如下所示:

<target name="phpcs">
    <exec executable="C:\path\to\Composer\vendor\bin\phpcs" taskname="phpcs">
        <arg value="--standard=PSR2" />
        <arg value="--extensions=php" />
        <arg value="--ignore=autoload.php" />
        <arg path="${basedir}/module/" />
    </exec>
</target>

但它不起作用,只会导致下一个错误:

D:\path\to\build.xml:34: Execute failed: java.io.IOException: Cannot run program "C:\path\to\Composer\vendor\bin\phpcs": CreateProcess error=193, %1 is not a valid Win32 application

这不是 Jenkins 问题,而是 Ant 问题。 Ant docu page for Exec:

中描述了原因和解决方案

Windows Users

The <exec> task delegates to Runtime.exec which in turn apparently calls ::CreateProcess. It is the latter Win32 function that defines the exact semantics of the call. In particular, if you do not put a file extension on the executable, only ".EXE" files are looked for, not ".COM", ".CMD" or other file types listed in the environment variable PATHEXT. That is only used by the shell.

Note that .bat files cannot in general by executed directly. One normally needs to execute the command shell executable cmd using the /c switch.

...

所以在我的例子中,工作目标规范如下所示:

<target name="phpcs">
    <exec executable="cmd" taskname="phpcs">
        <arg value="/c" />
        <arg value="C:\path\to\Composer\vendor\bin\phpcs" />
        <arg value="--standard=PSR2" />
        <arg value="--extensions=php" />
        <arg value="--ignore=autoload.php" />
        <arg path="${basedir}/module/" />
    </exec>
</target>

从现在开始构建通过。但是 phpcs:

仍然存在问题
phpcs:
    [phpcs] 'php' is not recognized as an internal or external command,
    [phpcs] operable program or batch file.
    [phpcs] Result: 1

通过在 Jenkins (Manage Jenkins -> Configure System -> Global properties -> Environment variables) 的 Path 环境变量中添加 PHP 即可轻松解决此问题:

就是这样。 :)