/* * Copyright 2000-2008 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jetbrains.buildServer.agent.rakerunner; import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.process.ProcessEvent; import com.intellij.execution.process.ProcessOutputTypes; import com.intellij.openapi.util.Key; import com.intellij.util.containers.HashMap; import jetbrains.buildServer.RunBuildException; import jetbrains.buildServer.agent.BuildAgentConfiguration; import jetbrains.buildServer.agent.rakerunner.utils.*; import jetbrains.buildServer.agent.runner.GenericProgramRunner; import jetbrains.buildServer.rakerunner.RakeRunnerConstants; import static jetbrains.buildServer.runner.BuildFileRunnerConstants.BUILD_FILE_PATH_KEY; import jetbrains.buildServer.runner.BuildFileRunnerUtil; import jetbrains.buildServer.util.PropertiesUtil; import jetbrains.buildServer.util.StringUtil; import org.apache.log4j.Logger; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * @author Roman.Chernyatchik */ public class RakeTasksRunner extends GenericProgramRunner implements RakeRunnerConstants { protected static final Logger LOG = Logger.getLogger(RakeTasksRunner.class.getName()); private final Set myFilesToDelete = new HashSet(); private final String RSPEC_RUNNER_OPTIONS_REQUIRE = "--require 'teamcity/spec/runner/formatter/teamcity/formatter'"; private final String RSPEC_RUNNERR_OPTIONS_FORMATTER = "--format Spec::Runner::Formatter::TeamcityFormatter:matrix"; @NonNls public String getType() { return RakeRunnerConstants.RUNNER_TYPE; } public boolean canRun(final BuildAgentConfiguration agentConfiguration) { return true; } protected void buildCommandLine(@NotNull final GeneralCommandLine cmd, @NotNull final File soourcesRootDir, @NotNull final Map runParams, @NotNull final Map buildParams) throws IOException, RunBuildException { // runParams - all server-ui options // buildParams - system properties (system.*), environment vars (env.*) final boolean inDebugMode = ConfigurationParamsUtil.isParameterEnabled(buildParams, RakeRunnerConstants.DEBUG_PROPERTY); final File buildFile = getBuildFile(runParams); try { // Special rake runner Environment properties final HashMap envMap = new HashMap(); // SDK patch addTestRunnerPatchFiles(runParams, buildParams, envMap); // Other runner ENV parameters // set runner mode to "buildserver" mode envMap.put(RAKE_MODE_KEY, RAKE_MODE_BUILDSERVER); // track invoke/execute stages if (ConfigurationParamsUtil.isParameterEnabled(runParams, SERVER_UI_RAKE_TRACE_INVOKE_EXEC_STAGES_ENABLED)) { envMap.put(RAKE_TRACE_INVOKE_EXEC_STAGES_ENABLED_KEY, Boolean.TRUE.toString()); } //TODO it seems it is deprecated //// explicit output capturer //if (!ConfigurationParamsUtil.isParameterEnabled(runParams, SERVER_UI_RAKE_OUTPUT_CAPTURER_ENABLED)) { // envMap.put(LOG_OUTPUT_CAPTURER_DISABLED_KEY, Boolean.TRUE.toString()); //} cmd.setEnvParams(envMap); // CommandLine options // Ruby interpreter cmd.setExePath(ConfigurationParamsUtil.getRubyInterpreterPath(runParams, buildParams)); // Rake runner script cmd.addParameter(RubyProjectSourcesUtil.getRakeRunnerPath()); // Rake options // Custom Rakefile if specified if (buildFile != null) { cmd.addParameter(RAKE_CMDLINE_OPTIONS_RAKEFILE); cmd.addParameter(buildFile.getAbsolutePath()); } // Other arguments final String otherArgsString = runParams.get(SERVER_UI_RAKE_ADDITIONAL_CMD_PARAMS_PROPERTY); if (!TextUtil.isEmptyOrWhitespaced(otherArgsString)) { addCmdlineArguments(cmd, otherArgsString); } // Tasks names final String tasks_names = runParams.get(SERVER_UI_RAKE_TASKS_PROPERTY); if (!PropertiesUtil.isEmptyOrNull(tasks_names)) { addCmdlineArguments(cmd, tasks_names); } //Test::Unit TESTOPTS final String testOpts = runParams.get(SERVER_UI_RAKE_TEST_UNIT_TESTOPTS_PROPERTY); if (!TextUtil.isEmptyOrWhitespaced(testOpts)) { cmd.addParameter(RAKE_TEST_UNIT_TESTOPTS_PARAM_NAME + "=" + testOpts.trim()); } final String specRunnerInitString = RSPEC_RUNNER_OPTIONS_REQUIRE + " " + RSPEC_RUNNERR_OPTIONS_FORMATTER; String specOpts = runParams.get(SERVER_UI_RSPEC_SPEC_OPTS_PROPERTY); if (TextUtil.isEmpty(specOpts)) { specOpts = specRunnerInitString; } else { specOpts = specOpts.trim() + " " + specRunnerInitString; } cmd.addParameter(RAKE__RSPEC_SPEC_OPTS_PARAM_NAME + "=" + specOpts.trim()); if (inDebugMode) { getBuildLogger().message("\n{RAKE RUNNER DEBUG}: CommandLine : \n" + cmd.getCommandLineString()); } } catch (MyBuildFailureException e) { failRakeTaskBuild(e); } if (inDebugMode) { getBuildLogger().message("\n{RAKE RUNNER DEBUG}: Working Directory: [" + soourcesRootDir.getCanonicalPath() + "]"); } } protected void failRakeTaskBuild(@NotNull final MyBuildFailureException e) throws RunBuildException { getBuildLogger().buildFailureDescription(e.getTitle()); throw new RunBuildException(e.getMessage()); } protected boolean shouldDumpOutputLinesOnError() { return false; } @Override protected void onOutput(final String lineWithoutLF, final Key lastOutputKey) { super.onOutput(lineWithoutLF, lastOutputKey); final String s = StringUtil.stripNewLine(lineWithoutLF); if (lastOutputKey == ProcessOutputTypes.STDERR) { // getBuildLogger().error(s); getBuildLogger().warning(s); } else { //TODO: think about SystemOutput getBuildLogger().message(s); } } protected void processWillBeTerminated(final Map runParameters, final ProcessEvent processEvent, final boolean b) { getBuildLogger().flush(); super.processWillBeTerminated(runParameters, processEvent, b); } protected void processTerminated(final RunEnvironment runEnvironment, final boolean isFailed) { getBuildLogger().flush(); super.processTerminated(runEnvironment, isFailed); // Remove tmp files for (File file : myFilesToDelete) { jetbrains.buildServer.util.FileUtil.delete(file); } myFilesToDelete.clear(); } private void addTestRunnerPatchFiles(final Map runParams, final Map buildParams, final HashMap envMap) throws MyBuildFailureException, RunBuildException { final String patchedRubySDKFilesRoot = RubyProjectSourcesUtil.getPatchedRubySDKFilesRoot(); // adds out patch to loadpath envMap.put(RUBYLIB_ENVIRONMENT_VARIABLE, OSUtil.appendToRUBYLIBEnvVariable(patchedRubySDKFilesRoot)); // due to patching loadpath we replace original autorunner but it is used buy our tests runner envMap.put(ORIGINAL_SDK_AUTORUNNER_PATH_KEY, RubySDKUtil.getSDKTestUnitAutoRunnerScriptPath(runParams, buildParams)); envMap.put(ORIGINAL_SDK_TESTRUNNERMEDIATOR_PATH_KEY, RubySDKUtil.getSDKTestUnitTestRunnerMediatorScriptPath(runParams, buildParams)); } private void addCmdlineArguments(@NotNull final GeneralCommandLine cmdLine, @NotNull final String argsString) { final List stringList = StringUtil.splitHonorQuotes(argsString, ' '); for (String arg : stringList) { cmdLine.addParameter(stripDoubleQuoteAroundValue(arg)); } } private String stripDoubleQuoteAroundValue(@NotNull final String str) { String text = str; if (StringUtil.startsWithChar(text, '\"')) { text = text.substring(1); } if (StringUtil.endsWithChar(text, '\"')) { text = text.substring(0, text.length() - 1); } return text; } @Nullable private File getBuildFile(Map runParameters) throws IOException, RunBuildException { final File buildFile; if (BuildFileRunnerUtil.isCustomBuildFileUsed(runParameters)) { buildFile = BuildFileRunnerUtil.getBuildFile(runParameters); } else { final String buildFilePath = runParameters.get(BUILD_FILE_PATH_KEY); if (PropertiesUtil.isEmptyOrNull(buildFilePath)) { //use rake defaults buildFile = null; } else { buildFile = BuildFileRunnerUtil.getBuildFile(runParameters); } } if (buildFile != null) { myFilesToDelete.add(buildFile); } return buildFile; } public static class MyBuildFailureException extends Exception { private final String msg; private final String title; public MyBuildFailureException(@NotNull final String msg, @NotNull final String title) { this.msg = msg; this.title = title; } public String getMessage() { return msg; } public String getTitle() { return title; } } }