/* * Copyright 2000-2010 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 java.io.File; import java.util.*; import jetbrains.buildServer.RunBuildException; import jetbrains.buildServer.agent.rakerunner.utils.*; import jetbrains.buildServer.rakerunner.RakeRunnerConstants; import jetbrains.buildServer.util.PropertiesUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.ruby.rvm.RVMSupportUtil; import static jetbrains.buildServer.rakerunner.RakeRunnerBundle.RUNNER_ERROR_TITLE_PROBLEMS_IN_CONF_ON_AGENT; /** * @author Roman.Chernyatchik */ public class RubySdkImpl implements RubySdk { private static final String GET_GEM_PATHS_SCRIPT = "require 'rubygems'; puts Gem.path"; private static final String RUBY_VERSION_SCRIPT = "print RUBY_VERSION"; @NonNls public static final String GET_LOAD_PATH_SCRIPT = "puts $LOAD_PATH"; private static final String RUBY19_DISABLE_GEMS_OPTION = "--disable-gems"; private final String myInterpreterPath; private final boolean myIsRvmSdk; private final String myGemsetName; private String[] myGemPaths; private boolean myIsRuby19; private RubyScriptRunner.Output myGemPathsLog; private RubyScriptRunner.Output myLoadPathsLog; private String[] myLoadPaths; protected RubySdkImpl(@NotNull final String interpreterPath, final boolean isRvmSdk, final String gemsetName) { myInterpreterPath = interpreterPath; myIsRvmSdk = isRvmSdk; myGemsetName = gemsetName; } @NotNull public String[] getGemPaths() { return myGemPaths; } @NotNull public boolean isRuby19() { return myIsRuby19; } @NotNull public RubyScriptRunner.Output getGemPathsFetchLog() { return myGemPathsLog; } @NotNull public RubyScriptRunner.Output getLoadPathsFetchLog() { return myLoadPathsLog; } @NotNull public String[] getLoadPath() { return myLoadPaths; } public static RubySdk createAndSetupSdk(final Map runParameters, final Map buildParameters, final Map buildConfEnvironment) throws RakeTasksBuildService.MyBuildFailureException, RunBuildException { // create final RubySdkImpl sdk = createSdkImpl(runParameters, buildParameters); // initialize // language level final boolean isRuby19 = isRuby19Interpreter(sdk, buildConfEnvironment); sdk.myIsRuby19 = isRuby19; // gem paths final RubyScriptRunner.Output gemPathsResult = RubySdkUtil.executeScriptFromSource(sdk, buildConfEnvironment, GET_GEM_PATHS_SCRIPT); sdk.myGemPathsLog = gemPathsResult; sdk.myGemPaths = TextUtil.splitByLines(gemPathsResult.getStdout()); // load path final RubyScriptRunner.Output loadPathResult = getLoadPaths(sdk, buildConfEnvironment, isRuby19); sdk.myLoadPathsLog = loadPathResult; sdk.myLoadPaths = TextUtil.splitByLines(loadPathResult.getStdout()); return sdk; } @NotNull public String getInterpreterPath() { return myInterpreterPath; } public boolean isRVMSdk() { return myIsRvmSdk; } @Nullable public String getRvmGemsetName() { return myGemsetName; } @NotNull public String getPresentableName() { if (isRVMSdk()) { final String gemsetName = getRvmGemsetName(); return myInterpreterPath + (gemsetName != null ? "[" + RVMSupportUtil.getGemsetSeparator() + gemsetName + "]" : ""); } else { return myInterpreterPath; } } @NotNull private static RubySdkImpl createSdkImpl(final Map runParameters, final Map buildParameters) throws RakeTasksBuildService.MyBuildFailureException, RunBuildException { // Check if path to ruby interpreter was explicitly set // and calculate corresponding interpreter path final String rubyInterpreterPath = determineRubyInterpreterPath(runParameters, buildParameters); // Final check that interpreter file exists final File rubyInterpreter = rubyInterpreterPath != null ? new File(rubyInterpreterPath) : null; try { if (rubyInterpreter == null || !rubyInterpreter.exists() || !rubyInterpreter.isFile()) { return throwInterpratatorDoesntExistError(rubyInterpreterPath); } } catch (SecurityException e) { //unknown error throw new RunBuildException(e.getMessage(), e); } // Check if interpreter is RVM based if (!RVMSupportUtil.isRVMInterpreter(rubyInterpreterPath)) { // not RVM return new RubySdkImpl(rubyInterpreterPath, false, null); } // RVM - determine gemset final String rvmGemset = determineGemset(rubyInterpreterPath, runParameters); return new RubySdkImpl(rubyInterpreterPath, true, rvmGemset); } private static RubySdkImpl throwInterpratatorDoesntExistError(final String rubyInterpreterPath) throws RakeTasksBuildService.MyBuildFailureException { final String msg = "Ruby interpreter '" + rubyInterpreterPath + "' doesn't exist or isn't a file or isn't a valid RVM interpreter name."; throw new RakeTasksBuildService.MyBuildFailureException(msg, RUNNER_ERROR_TITLE_PROBLEMS_IN_CONF_ON_AGENT); } private static String determineGemset(@NotNull final String rubyInterpreterPath, final Map runParameters) throws RakeTasksBuildService.MyBuildFailureException { // RVM - determine gemset final String rvmGemset = getGemsetName(runParameters); if (rvmGemset == null) { return rvmGemset; } // validate final boolean isValid = RVMSupportUtil.isGemsetExists(rvmGemset, rubyInterpreterPath); if (!isValid) { final String gemsets = RVMSupportUtil.dumpAvailableGemsets(rubyInterpreterPath); final String msg = "Gemset '" + rvmGemset + "' isn't defined for ruby interpreter '" + rubyInterpreterPath + "'. Please create the gemset at first.\n" + (gemsets == null ? "" : "\nAvailable gemsets: " + gemsets); throw new RakeTasksBuildService.MyBuildFailureException(msg, RUNNER_ERROR_TITLE_PROBLEMS_IN_CONF_ON_AGENT); } return rvmGemset; } @NotNull private static String determineRubyInterpreterPath(final Map runParameters, final Map buildParameters) throws RakeTasksBuildService.MyBuildFailureException { final String uiRubyInterpreterSetting = runParameters.get(RakeRunnerConstants.SERVER_UI_RUBY_INTERPRETER); // use default interpreter if (PropertiesUtil.isEmptyOrNull(uiRubyInterpreterSetting)) { return findSystemInterpreterPath(buildParameters); } // custom if (FileUtil.checkIfExists(uiRubyInterpreterSetting)) { return uiRubyInterpreterSetting; } // probably rvm short sdk name // short name shouldn't contain slashes if (!uiRubyInterpreterSetting.contains("/") && !uiRubyInterpreterSetting.contains("\\")) { // at first lets check that it isn't "system" interpreter if (RVMSupportUtil.isSystemRuby(uiRubyInterpreterSetting)) { return findSystemInterpreterPath(buildParameters); } // build dist/gemsets table, match ref with dist. name final String rvmGemset = getGemsetName(runParameters); final String distName = RVMSupportUtil.determineSuitableRVMSdkDist(uiRubyInterpreterSetting, rvmGemset); if (distName != null) { return RVMSupportUtil.suggestInterpretatorPath(distName); } final String msg = "Gemset '" + rvmGemset + "' isn't defined for Ruby interpreter '" + uiRubyInterpreterSetting + "' or the interpreter doesn't exist or isn't a file or isn't a valid RVM interpreter name."; throw new RakeTasksBuildService.MyBuildFailureException(msg, RUNNER_ERROR_TITLE_PROBLEMS_IN_CONF_ON_AGENT); } throwInterpratatorDoesntExistError(uiRubyInterpreterSetting); return null; } private static String findSystemInterpreterPath(final Map buildParameters) throws RakeTasksBuildService.MyBuildFailureException { // find in $PATH final String path = OSUtil.findRubyInterpreterInPATH(buildParameters); if (path != null) { return path; } final String msg = "Unable to find Ruby interpreter in PATH."; throw new RakeTasksBuildService.MyBuildFailureException(msg, RUNNER_ERROR_TITLE_PROBLEMS_IN_CONF_ON_AGENT); } @Nullable private static String getGemsetName(final Map runParameters) { final String uiRVMGemsetString = runParameters.get(RakeRunnerConstants.SERVER_UI_RUBY_RVM_GEMSET_NAME); return !PropertiesUtil.isEmptyOrNull(uiRVMGemsetString) ? uiRVMGemsetString.trim() : null; } private static boolean isRuby19Interpreter(@NotNull final RubySdk sdk, final Map buildConfEnvironment) { boolean isRuby19 = false; final RubyScriptRunner.Output rubyVersionResult = RubySdkUtil.executeScriptFromSource(sdk, buildConfEnvironment, RUBY_VERSION_SCRIPT); final String stdOut = rubyVersionResult.getStdout(); if (stdOut != null && stdOut.contains("1.9.")) { isRuby19 = true; } return isRuby19; } private static RubyScriptRunner.Output getLoadPaths(@NotNull final RubySdk sdk, final Map buildConfEnvironment, final boolean ruby19) { // LOAD_PATH way final String[] rubyArgs; if (ruby19) { // filter gem paths in case of Ruby 1.9 (use --disable-gems) rubyArgs = new String[]{RUBY19_DISABLE_GEMS_OPTION}; } else { rubyArgs = new String[0]; } return RubySdkUtil.executeScriptFromSource(sdk, buildConfEnvironment, GET_LOAD_PATH_SCRIPT, rubyArgs); } }