All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ow2.mind.compilation.BasicCompilationCommandExecutor Maven / Gradle / Ivy

There is a newer version: 2.0
Show newest version
/**
 * Copyright (C) 2009 STMicroelectronics
 *
 * This file is part of "Mind Compiler" is free software: you can redistribute 
 * it and/or modify it under the terms of the GNU Lesser General Public License 
 * as published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT 
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 *
 * Contact: [email protected]
 *
 * Authors: Matthieu Leclercq
 * Contributors: 
 */

package org.ow2.mind.compilation;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.objectweb.fractal.adl.ADLException;
import org.objectweb.fractal.adl.CompilerError;
import org.objectweb.fractal.adl.error.GenericErrors;
import org.objectweb.fractal.adl.util.FractalADLLogManager;
import org.ow2.mind.ForceRegenContextHelper;
import org.ow2.mind.error.ErrorManager;

import com.google.inject.Inject;

public class BasicCompilationCommandExecutor
    implements
      CompilationCommandExecutor {

  protected static Logger    depLogger                  = FractalADLLogManager
                                                            .getLogger("dep");

  public static final String CONCURENT_JOBS_CONTEXT_KEY = "jobs";

  public static final String FAIL_FAST_CONTEXT_KEY      = "fail-fast";

  @Inject
  protected ErrorManager     errorManagerItf;

  // ---------------------------------------------------------------------------
  // Implementation of the CompilationCommandExecutor interface
  // ---------------------------------------------------------------------------

  public boolean exec(final Collection commands,
      final Map context) throws ADLException,
      InterruptedException {
    final Map> depGraph = new HashMap>();
    final LinkedList readyTask = new LinkedList();
    final boolean force = ForceRegenContextHelper.getForceRegen(context);
    buildDepGraph(commands, depGraph, readyTask, force);

    if (depGraph.isEmpty() && readyTask.isEmpty()) {
      if (depLogger.isLoggable(Level.INFO))
        depLogger.info("Nothing to be done, compiled files are up-to-dates.");
      return true;
    }

    int jobs = 1;
    Object o = context.get(CONCURENT_JOBS_CONTEXT_KEY);
    if (o instanceof Integer) {
      jobs = (Integer) o;
    }
    boolean failFast = true;
    o = context.get(FAIL_FAST_CONTEXT_KEY);
    if (o instanceof Boolean) {
      failFast = (Boolean) o;
    }
    return execDepGraph(jobs, depGraph, readyTask, failFast);
  }

  protected void buildDepGraph(final Collection commands,
      final Map> depGraph,
      final List readyTask, final boolean forced) {

    final Map cmdInfos = new IdentityHashMap();
    for (final CompilationCommand cmd : commands) {
      cmd.prepare();
      cmdInfos.put(cmd, new CommandInfo(cmd));
    }

    /*
     * Build the fileProviders map that associates files to the command that
     * produces them
     */
    final Map fileProducers = new HashMap();
    for (final CompilationCommand cmd : commands) {
      final CommandInfo cmdInfo = cmdInfos.get(cmd);
      for (final File outputFile : cmd.getOutputFiles()) {
        final CommandInfo previousProviders = fileProducers.put(outputFile,
            cmdInfo);
        if (previousProviders != null) {
          throw new CompilerError(GenericErrors.INTERNAL_ERROR,
              "Multiple provider of the same output-file \"" + outputFile
                  + "\" (" + cmd.getDescription() + " and "
                  + previousProviders.command.getDescription() + ").");
        }
      }
    }

    /*
     * The fileConsumers map associated a File to the tasks that consume it.
     */
    final Map> fileConsumers = new HashMap>();

    /*
     * Build the depGraph map that associates commands that produce files to the
     * collection of commands that consume them . Use CommandInfo class to store
     * additional info on command dependencies. Build also the readyTask list
     * that contains the commands that are ready to be executed (i.e. the
     * commands that do not depend on other commands).
     */
    for (final CompilationCommand cmd : commands) {
      /*
       * Create a CommandInfo object for the current command. This object
       * contains the number of commands, this one depends on. (i.e the number
       * of commands that must be executed before this one can be executed).
       */
      final CommandInfo cmdInfo = cmdInfos.get(cmd);
      for (final File inputFile : cmd.getInputFiles()) {
        /* Fill-in the fileConsumer map. */
        Collection fileConsumer = fileConsumers.get(inputFile);
        if (fileConsumer == null) {
          fileConsumer = new ArrayList();
          fileConsumers.put(inputFile, fileConsumer);
        }
        fileConsumer.add(cmdInfo);

        /* Find command that provides this input files */
        final CommandInfo provider = fileProducers.get(inputFile);
        if (provider != null) {
          /*
           * Add the current cmd command as a command that depends on the
           * provider command in the depGraph map.
           */
          Collection deps = depGraph.get(provider);
          if (deps == null) {
            deps = new LinkedList();
            depGraph.put(provider, deps);
          }
          deps.add(cmdInfo);
          /* Add the provider task as a dependency of the current cmd command. */
          cmdInfo.dependencies.add(provider);
        } else if (!inputFile.exists()) {
          throw new CompilerError(GenericErrors.INTERNAL_ERROR,
              "Missing input-file \"" + inputFile
                  + "\" of compilation command : " + cmd.getDescription() + ".");
        }
      }
      if (forced && cmdInfo.dependencies.isEmpty()) {
        /*
         * The current cmd command has no dependency, it is ready to be
         * executed.
         */
        readyTask.add(cmdInfo);
      }
    }

    /* If in forced mode, do not expunge up-to-date tasks, execute all of them. */
    if (forced) return;

    /*
     * Expunge tasks that are up-to-dates. Travel the readyTask list and checks
     * if output files are more recent than input files.
     */
    // cache of output file timestamp
    final Map outputFileTimestamps = new HashMap();
    // cache of input file timestamp
    final Map inputFileTimestamps = new HashMap();

    for (final CompilationCommand cmd : commands) {
      final CommandInfo cmdInfo = cmdInfos.get(cmd);
      getOutputCommandTimestamp(cmdInfo, fileConsumers, outputFileTimestamps);
    }
    for (final CompilationCommand cmd : commands) {
      final CommandInfo cmdInfo = cmdInfos.get(cmd);
      getInputCommandTimestamp(cmdInfo, fileProducers, inputFileTimestamps);
    }

    for (final CompilationCommand cmd : commands) {
      final CommandInfo cmdInfo = cmdInfos.get(cmd);

      if (cmdInfo.maxInputTimestamp > cmdInfo.maxOutputTimestamp) {
        cmdInfo.setMustBeExecuted(depGraph, fileProducers);
      }
    }

    final Collection expungedTasks = new ArrayList();
    for (final CompilationCommand cmd : commands) {
      final CommandInfo cmdInfo = cmdInfos.get(cmd);
      if (cmdInfo.mustBeExecuted) {
        if (depLogger.isLoggable(Level.FINE))
          depLogger.fine("Task '" + cmdInfo.command.getDescription()
              + "' Input file '" + cmdInfo.maxInputFile
              + "' is more recent than output file '" + cmdInfo.maxOutputFile
              + "', recompile.");
      } else {

        expungedTasks.add(cmdInfo);
        depGraph.remove(cmdInfo);
        if (depLogger.isLoggable(Level.FINE))
          depLogger.fine("Command '" + cmd.getDescription()
              + "' is up to date, do not recompile.");
      }
    }

    for (final CompilationCommand cmd : commands) {
      final CommandInfo cmdInfo = cmdInfos.get(cmd);
      cmdInfo.dependencies.removeAll(expungedTasks);
      if (cmdInfo.dependencies.isEmpty() && !expungedTasks.contains(cmdInfo)) {
        /*
         * The current cmd command has no dependency, it is ready to be
         * executed.
         */
        readyTask.add(cmdInfo);
      }
    }
  }

  /**
   * Returns the timestamp of the given outputFile. the Timestamp of an
   * outputFile is defines as follow :
   * 
    *
  • If the file exists, its timestamp is the value returned by * {@link File#lastModified()}.
  • *
  • Otherwise *
      *
    • The timestamp is the maximum timestamp of the consumer commands (the * commands that use this file as input file), as defined in * {@link #getOutputCommandTimestamp(CommandInfo, Map, Map)}, or zero if one * of its consumer command timestamp is null.
    • *
    • If the file is a final file (i.e. it is not the input-file of a * compilation command), its timestamp is zero (since it does not exist).
    • *
    *
*/ protected long getOutputFileTimestamp(final File outputFile, final Map> fileConsumers, final Map outputFileTimestamps) { Long ts = outputFileTimestamps.get(outputFile); if (ts == null) { if (outputFile.exists()) { ts = outputFile.lastModified(); if (depLogger.isLoggable(Level.FINEST)) depLogger.finest("Output file '" + outputFile + "' exists, its timestamp is " + ts + "."); } else { /* * the outputFile does not exist. Its timestamp is the max of the * timestamps of its consumer commands */ final Collection consumers = fileConsumers.get(outputFile); if (consumers == null) { /* * the outputFile is not consumed by any command. It is a final output * file that does not exist. Its timestamp is 0. */ ts = 0L; } else { long maxTs = 0L; for (final CommandInfo cmdInfo : consumers) { final long cmdTs = getOutputCommandTimestamp(cmdInfo, fileConsumers, outputFileTimestamps); if (cmdTs == 0L) { maxTs = 0L; break; } if (cmdTs > maxTs) { maxTs = cmdTs; } } ts = maxTs; } if (depLogger.isLoggable(Level.FINEST)) depLogger.finest("Output file '" + outputFile + "' does not exist, its inferred timestamp is " + ts + "."); } outputFileTimestamps.put(outputFile, ts); } return ts; } protected long getOutputCommandTimestamp(final CommandInfo cmdInfo, final Map> fileConsumers, final Map outputFileTimestamps) { if (cmdInfo.maxOutputTimestamp == -1L) { if (cmdInfo.command.forceExec()) { cmdInfo.maxOutputTimestamp = 0L; if (depLogger.isLoggable(Level.FINEST)) depLogger.finest("Task '" + cmdInfo.command.getDescription() + "' is forced, set outputTimestamp to 0."); } else { /* For each file produced by the command */ for (final File outputFile : cmdInfo.command.getOutputFiles()) { /* Get the timestamp of the file. */ final long outputFileTs = getOutputFileTimestamp(outputFile, fileConsumers, outputFileTimestamps); if (outputFileTs == 0L) { cmdInfo.maxOutputTimestamp = 0L; cmdInfo.maxOutputFile = outputFile; if (depLogger.isLoggable(Level.FINEST)) depLogger.finest("Task '" + cmdInfo.command.getDescription() + "' output file '" + outputFile + "' must be regenerated, set output timestamp to 0."); break; } if (outputFileTs > cmdInfo.maxOutputTimestamp) { cmdInfo.maxOutputTimestamp = outputFileTs; cmdInfo.maxOutputFile = outputFile; if (depLogger.isLoggable(Level.FINEST)) depLogger.finest("Task '" + cmdInfo.command.getDescription() + "' set output timestamp to timestamp of output file '" + outputFile + "' : " + outputFileTs); } } } } return cmdInfo.maxOutputTimestamp; } /** * Returns the timestamp of the given inputFile. the Timestamp of an inputFile * is defines as follow : *
    *
  • If the file exists, its timestamp is the value returned by * {@link File#lastModified()}.
  • *
  • Otherwise the timestamp is the timestamp of the producer commands (the * commands that produce this file as output file), as defined in * {@link #getInputCommandTimestamp(CommandInfo, Map, Map)}.
  • *
*/ protected long getInputFileTimestamp(final File inputFile, final Map fileProducers, final Map inputFileTimestamps) { Long ts = inputFileTimestamps.get(inputFile); if (ts == null) { if (inputFile.exists()) { ts = inputFile.lastModified(); if (depLogger.isLoggable(Level.FINEST)) depLogger.finest("Input file '" + inputFile + "' exists, its timestamp is " + ts + "."); } else { /* * the inputFile does not exist. Its timestamp is the timestamps of its * producer command */ final CommandInfo producer = fileProducers.get(inputFile); ts = getInputCommandTimestamp(producer, fileProducers, inputFileTimestamps); if (depLogger.isLoggable(Level.FINEST)) depLogger.finest("Input file '" + inputFile + "' does not exist, its inferred timestamp is " + ts + "."); } inputFileTimestamps.put(inputFile, ts); } return ts; } protected long getInputCommandTimestamp(final CommandInfo cmdInfo, final Map fileProducers, final Map inputFileTimestamps) { if (cmdInfo.maxInputTimestamp == -1L) { if (cmdInfo.command.forceExec()) { cmdInfo.maxInputTimestamp = Long.MAX_VALUE; if (depLogger.isLoggable(Level.FINEST)) depLogger.finest("Task '" + cmdInfo.command.getDescription() + "' is forced, set inputTimestamp to MAX."); } else { /* For each file consumed by the command */ for (final File inputFile : cmdInfo.command.getInputFiles()) { /* Get the timestamp of the file. */ final long inputFileTs = getInputFileTimestamp(inputFile, fileProducers, inputFileTimestamps); if (inputFileTs > cmdInfo.maxInputTimestamp) { cmdInfo.maxInputTimestamp = inputFileTs; cmdInfo.maxInputFile = inputFile; if (depLogger.isLoggable(Level.FINEST)) depLogger.finest("Task '" + cmdInfo.command.getDescription() + "' set task input timestamp to timestamp of input file '" + inputFile + "' : " + inputFileTs); } } } } return cmdInfo.maxInputTimestamp; } protected boolean execDepGraph(final int nbJobs, final Map> depGraph, final LinkedList readyTask, final boolean failFast) throws ADLException, InterruptedException { if (nbJobs == 1) { return execDepGraphSynchronous(depGraph, readyTask, failFast); } else { final ExecutionState exceptionHolder = new ExecutionState(readyTask, depGraph, nbJobs, failFast); return exceptionHolder.terminate(); } } protected boolean execDepGraphSynchronous( final Map> depGraph, final LinkedList readyTask, final boolean failFast) throws ADLException, InterruptedException { boolean result = true; while (!readyTask.isEmpty()) { final CommandInfo cmdInfo = readyTask.removeFirst(); boolean execOK; try { execOK = cmdInfo.command.exec(); } catch (final ADLException e) { execOK = false; } if (execOK) { commandEnded(cmdInfo, depGraph, readyTask); } else { commandFailed(cmdInfo, depGraph, readyTask); result = false; if (failFast) return result; } } assert depGraph.isEmpty(); return result; } protected boolean commandEnded(final CommandInfo cmd, final Map> depGraph, final LinkedList readyTask) { boolean commandUnlocked = false; final Collection deps = depGraph.remove(cmd); if (deps != null) { final Iterator iter = deps.iterator(); while (iter.hasNext()) { final CommandInfo cmdInfo = iter.next(); assert cmdInfo.dependencies.contains(cmd); cmdInfo.dependencies.remove(cmd); if (cmdInfo.dependencies.isEmpty()) { // every dependency of command are done, place it in ready task. iter.remove(); readyTask.addLast(cmdInfo); commandUnlocked = true; } } } return commandUnlocked; } protected void commandFailed(final CommandInfo cmd, final Map> depGraph, final LinkedList readyTask) { final Collection deps = depGraph.remove(cmd); if (deps != null) { for (final CommandInfo cmdInfo : deps) { commandFailed(cmdInfo, depGraph, readyTask); } } } protected final class ExecutionState { final LinkedList readyTask; final Map> depGraph; final boolean failFast; boolean result = true; final Lock lock = new ReentrantLock(); Condition condition = lock .newCondition(); Exception exception; int nbRunningThread; ExecutionState(final LinkedList readyTask, final Map> depGraph, final int nbJob, final boolean failFast) { this.readyTask = readyTask; this.depGraph = depGraph; this.failFast = failFast; lock.lock(); try { for (int i = 0; i < nbJob; i++) { new Thread() { @Override public void run() { work(); } }.start(); } nbRunningThread = nbJob; } finally { lock.unlock(); } } void work() { lock.lock(); try { // execute a command if : // - a task is ready to be executed // - AND // * we are not in fail-fast mode // * OR we are in fail-fast mode AND there is no error (i.e. // result == true AND exception == null) // while (!readyTask.isEmpty() && (!failFast || (result && exception == null))) { final CommandInfo cmdInfo = readyTask.removeFirst(); lock.unlock(); boolean execOK; try { execOK = cmdInfo.command.exec(); } catch (final ADLException e) { execOK = false; } catch (final Exception e) { lock.lock(); if (exception != null) { exception = e; } break; } if (!execOK) { lock.lock(); commandFailed(cmdInfo, depGraph, readyTask); result = false; if (failFast) { break; } else { continue; } } lock.lock(); commandEnded(cmdInfo, depGraph, readyTask); } } finally { nbRunningThread--; if (nbRunningThread <= 0) { condition.signalAll(); } lock.unlock(); } } boolean terminate() throws ADLException, InterruptedException { lock.lock(); try { while (nbRunningThread > 0) { condition.await(); } } finally { lock.unlock(); } if (exception != null) { throw new CompilerError(GenericErrors.INTERNAL_ERROR, exception, "Unexpected error"); } return result; } } protected static final class CommandInfo { final CompilationCommand command; Collection dependencies = new ArrayList(); File maxOutputFile; long maxOutputTimestamp = -1L; File maxInputFile; long maxInputTimestamp = -1L; boolean mustBeExecuted = false; CommandInfo(final CompilationCommand command) { this.command = command; } void setMustBeExecuted( final Map> depGraph, final Map fileProducers) { if (!mustBeExecuted) { this.mustBeExecuted = true; final Collection cmds = depGraph.get(this); if (cmds != null) { for (final CommandInfo cmdInfo : cmds) { cmdInfo.setMustBeExecuted(depGraph, fileProducers); } } for (final File inputFile : command.getInputFiles()) { if (!inputFile.exists()) { /* * The inputFile in not present, the task that produces it must be * executed also. */ fileProducers.get(inputFile).setMustBeExecuted(depGraph, fileProducers); } } } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy