code: /**
   * This file is part of Eclipse Steady.
   *
   * 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.
   *
   * SPDX-License-Identifier: Apache-2.0
   * SPDX-FileCopyrightText: Copyright (c) 2018-2020 SAP SE or an SAP affiliate company and Eclipse Steady contributors
   */
  package org.eclipse.steady.python.virtualenv;
  
  import java.io.IOException;
  import java.nio.file.FileVisitOption;
  import java.nio.file.FileVisitResult;
  import java.nio.file.Files;
  import java.nio.file.Path;
  import java.nio.file.Paths;
  import java.nio.file.SimpleFileVisitor;
  import java.nio.file.attribute.BasicFileAttributes;
  import java.util.HashSet;
  import java.util.Set;
  
  import org.apache.logging.log4j.Logger;
  import org.eclipse.steady.python.ProcessWrapperException;
  import org.eclipse.steady.python.pip.PipInstalledPackage;
  import org.eclipse.steady.python.pip.PipWrapper;
  import org.eclipse.steady.python.pip.PyWrapper;
  import org.eclipse.steady.python.utils.PythonConfiguration;
  import org.eclipse.steady.shared.util.DirUtil;
  import org.eclipse.steady.shared.util.FileUtil;
  import org.eclipse.steady.shared.util.VulasConfiguration;
  
  /**
   * <p>VirtualenvWrapper class.</p>
   */
  public class VirtualenvWrapper {
  
    private static final String SETUP_PY = "setup.py";
  
    private static final boolean IS_WIN = System.getProperty("os.name").contains("Windows");
  
    private static final Logger log = org.apache.logging.log4j.LogManager.getLogger();
  
    private Path pathToVirtualenvExecutable = null;
  
    private Path pathToPythonProject = null;
  
    private String projectName = null;
  
    private Path pathToVirtualenv = null;
  
    private Set<PipInstalledPackage> installedPackages = null;
  
    /**
     * Assumes that the virtualenv executable is part of the PATH environment variable.
     *
     * @param _path_to_python_project a {@link java.nio.file.Path} object.
     * @throws java.lang.IllegalArgumentException if any.
     * @throws org.eclipse.steady.python.ProcessWrapperException if any.
     */
    public VirtualenvWrapper(Path _path_to_python_project)
        throws IllegalArgumentException, ProcessWrapperException {
      this(Paths.get("virtualenv"), _path_to_python_project);
    }
  
    /**
     * Creates a virtual environment for the project located at the given path.
     * This path is expected to contain a setup.py file.
     *
     * @param _path_to_virtualenv a {@link java.nio.file.Path} object.
     * @param _path_to_python_project a {@link java.nio.file.Path} object.
     * @throws java.lang.IllegalArgumentException if any.
     * @throws org.eclipse.steady.python.ProcessWrapperException if any.
     */
    public VirtualenvWrapper(Path _path_to_virtualenv, Path _path_to_python_project)
        throws IllegalArgumentException, ProcessWrapperException {
  
      // Check it is a directory with a file "setup.py"
      if (!FileUtil.isAccessibleDirectory(_path_to_python_project))
        throw new IllegalArgumentException(
            "Project path ["
                + _path_to_python_project
                + "] does not point to an accessible directory");
      if (!DirUtil.containsFile(_path_to_python_project.toFile(), SETUP_PY))
        throw new IllegalArgumentException(
            "Project path ["
                + _path_to_python_project
                + "] does not contain the file ["
                + SETUP_PY
                + "]");
  
      this.pathToVirtualenvExecutable = _path_to_virtualenv;
      this.pathToPythonProject = _path_to_python_project.toAbsolutePath();
      this.projectName =
          this.pathToPythonProject.getName(this.pathToPythonProject.getNameCount() - 1).toString();
  
      // Create the virtualenv
      try {
        this.pathToVirtualenv =
            FileUtil.createTmpDir("vulas-virtualenv-" + this.projectName + "-").toAbsolutePath();
      } catch (IOException e) {
        throw new ProcessWrapperException("Cannot create tmp directory: " + e.getMessage());
      }
      this.createVirtualenvForProject(this.pathToVirtualenv);
  
      // Copy the project folder
      this.copyProjectDirectory();
  
      // Create Vulas directories vulas/log and vulas/download
  
      // Call PIP install
      final Path prj_path = Paths.get(this.pathToVirtualenv.toString(), this.projectName);
      installedPackages = this.getPipWrapper().installPackages(prj_path);
  
      // Call setup
      // final Path setup_path = Paths.get(this.pathToVirtualenv.toString(), this.projectName,
      // SETUP_PY);
      // this.getPyWrapper().runScript(setup_path, Arrays.asList(new String[] {"install"}));
    }
  
    /**
     * <p>Getter for the field <code>projectName</code>.</p>
     *
     * @return a {@link java.lang.String} object.
     */
    public String getProjectName() {
      return projectName;
    }
  
    /**
     * <p>Getter for the field <code>pathToVirtualenv</code>.</p>
     *
     * @return a {@link java.nio.file.Path} object.
     */
    public Path getPathToVirtualenv() {
      return pathToVirtualenv;
    }
  
    /**
     * <p>Getter for the field <code>installedPackages</code>.</p>
     *
     * @return a {@link java.util.Set} object.
     */
    public Set<PipInstalledPackage> getInstalledPackages() {
      return this.installedPackages;
    }
  
    /**
     * <p>getPipWrapper.</p>
     *
     * @return a {@link org.eclipse.steady.python.pip.PipWrapper} object.
     * @throws org.eclipse.steady.python.ProcessWrapperException if any.
     */
    public PipWrapper getPipWrapper() throws ProcessWrapperException {
      if (IS_WIN)
        return new PipWrapper(
            Paths.get(this.pathToVirtualenv.toString(), "Scripts", "pip"), this.pathToVirtualenv);
      else
        return new PipWrapper(
            Paths.get(this.pathToVirtualenv.toString(), "bin", "pip"), this.pathToVirtualenv);
    }
  
    /**
     * <p>getPyWrapper.</p>
     *
     * @return a {@link org.eclipse.steady.python.pip.PyWrapper} object.
     * @throws org.eclipse.steady.python.ProcessWrapperException if any.
     */
    public PyWrapper getPyWrapper() throws ProcessWrapperException {
      if (IS_WIN)
        return new PyWrapper(
            Paths.get(this.pathToVirtualenv.toString(), "Scripts", "python"), this.pathToVirtualenv);
      else
        return new PyWrapper(
            Paths.get(this.pathToVirtualenv.toString(), "bin", "python"), this.pathToVirtualenv);
    }
  
    /**
     * Creates a virtual environment for the project located at the given path in a temporary directory.
     * @param _project_path
     */
    private void createVirtualenvForProject(Path _path_to_virtual_env)
        throws ProcessWrapperException {
      Path ve = null;
      try {
        log.info("Create virtualenv in [" + _path_to_virtual_env + "]");
  
        // Create the virtual env inside this temp directory
        ProcessBuilder pb = null;
        if (VulasConfiguration.getGlobal().isEmpty(PythonConfiguration.PY_PY_PATH)) {
          pb =
              new ProcessBuilder(
                  this.pathToVirtualenvExecutable.toString(), _path_to_virtual_env.toString());
        } else {
          pb =
              new ProcessBuilder(
                  this.pathToVirtualenvExecutable.toString(),
                  "--python",
                  VulasConfiguration.getGlobal()
                      .getConfiguration()
                      .getString(PythonConfiguration.PY_PY_PATH),
                  _path_to_virtual_env.toString());
        }
  
        // Create temp. directory for out and err streams
        final Path out = Paths.get(_path_to_virtual_env.toString(), "virtualenv-out.txt");
        final Path err = Paths.get(_path_to_virtual_env.toString(), "virtualenv-err.txt");
  
        // Redirect out and err
        pb.redirectOutput(out.toFile());
        pb.redirectError(err.toFile());
  
        // Start and wait
        final Process process = pb.start();
        final int exit_code = process.waitFor();
  
        // Success
        if (exit_code != 0) {
          final String error_msg = FileUtil.readFile(err);
          throw new ProcessWrapperException(error_msg);
        }
      } catch (IOException e) {
        throw new ProcessWrapperException(e.getMessage(), e);
      } catch (InterruptedException e) {
        throw new ProcessWrapperException(e.getMessage(), e);
      }
    }
  
    private Path copyProjectDirectory() throws ProcessWrapperException {
      Path new_dir = null;
      try {
        Files.walkFileTree(
            this.pathToPythonProject,
            new HashSet<FileVisitOption>(),
            Integer.MAX_VALUE,
            new CopyFileVisitor(this.pathToPythonProject, this.pathToVirtualenv));
      } catch (Exception e) {
        new_dir = null;
        log.error(
            "Cannot copy project dir ["
                + this.pathToPythonProject
                + "] to virtual env ["
                + this.pathToVirtualenvExecutable
                + "]:"
                + e.getMessage());
        throw new ProcessWrapperException(
            "Cannot copy project dir ["
                + this.pathToPythonProject
                + "] to virtual env ["
                + this.pathToVirtualenvExecutable
                + "]:"
                + e.getMessage());
      }
      return new_dir;
    }
  
    private static class CopyFileVisitor extends SimpleFileVisitor<Path> {
  
      private static final Logger log = org.apache.logging.log4j.LogManager.getLogger();
  
      private Path src = null;
      private Path tgt = null;
  
      public CopyFileVisitor(Path _src, Path _tgt) {
        this.src = _src;
        this.tgt = _tgt;
      }
  
      @Override
      public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
          throws IOException {
        final Path relp = this.src.getParent().relativize(dir.toAbsolutePath());
        final Path newp = Paths.get(tgt.toString(), relp.toString());
        try {
          Files.createDirectories(newp);
        } catch (Exception e) {
          log.error("Cannot copy [" + dir + "] to [" + newp + "]: " + e.getMessage());
        }
        return FileVisitResult.CONTINUE;
      }
  
      @Override
      public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
        final Path relp = this.src.getParent().relativize(file);
        final Path newf = Paths.get(this.tgt.toString(), relp.toString());
        try {
          Files.copy(file, newf);
        } catch (Exception e) {
          log.error("Cannot copy [" + file + "] to [" + newf + "]: " + e.getMessage());
        }
        return FileVisitResult.CONTINUE;
      }
    }
  }
  