2005/5/9

     
 

CppSourceDependTask.cpp

artefaktur
// -*- mode:C++; tab-width:2; c-basic-offset:2; indent-tabs-mode:nil -*- 
//
// Copyright (C) 2000-2005 by Roger Rene Kommer / artefaktur, Kassel, Germany.
// 
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public License (LGPL).
// 
// 
// This library 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 
// License ACDK-FreeLicense document enclosed in the distribution
// for more for more details.
// This file is part of the Artefaktur Component Development Kit:
//                         ACDK
// 
// Please refer to
// - http://www.acdk.de
// - http://www.artefaktur.com
// - http://acdk.sourceforge.net
// for more information.
// 
#include "CppSourceDependTask.h"
#include "ChDir.h"
#include <acdk/io/File.h>
#include <acdk/io/FileReader.h>
#include <acdk/io/StreamTokenizer.h>
#include <acdk/lang/System.h>
#include <acdk/util/SysDate.h>
#include <acdk/io/ByteToCharReader.h>
#include <acdk/locale/Encoding.h>

namespace acdk {
namespace make {

//#define LOCAL_DEBUG
#if defined(LOCAL_DEBUG)

RString spaces(int count)
{
  StringBuffer sb(20);
  for (int i = 0; i < count; ++i)
    sb.append(" ");
  return sb.toString();
}

#define DOUT(strexpr) \
do { \
  StringBuffer sb; \
  sb << strexpr; \
  System::out->println(sb.toString()); \
} while (false)
#else
/FONT>
#define DOUT(strexpr) do { } while(false)
#endif
/FONT>


bool 
CppSourceDependTask::noSourceDeps = false;
bool 
CppSourceDependTask::onlyDirectIncludes = false;
bool 
CppSourceDependTask::recursiveDeps = true;

bool 
CppSourceDependTask::useCache = true;

void 
CppSourceDependTask::addIncludeDir(IN(RString) str)
{
  _includeDirs->append(str);
}

USING_CLASS(acdk::io::, StreamTokenizer);
USING_CLASS(acdk::io::, File);

struct DepCacheEntry
{
  RString filename;
  jlong modtime;
  jlong minmodtime;
  
  DepCacheEntry(IN(RString) fname, jlong mt)
  : filename(fname)
  , modtime(mt)
  , minmodtime(mt)
  {
  }
  
  void setMinLastModified(jlong mt)
  {
    if (minmodtime < mt)
    {
      DOUT("    MinTime: " << filename);
      minmodtime = mt;
    }
  }
};

struct DepCache
{
  ::acdk::lang::sys::core_vector<DepCacheEntry> _deps;
  ::acdk::lang::sys::core_mutex _mutex;
  typedef ::acdk::lang::sys::core_lock_guard< ::acdk::lang::sys::core_mutex> LockGuard;
  void add(const DepCacheEntry& de)
  {
    LockGuard _lock(_mutex);
    _deps.push_back(de);
  }
  DepCacheEntry find(IN(RString) filename)
  {
    LockGuard _lock(_mutex);
    for (int i = 0; i < _deps.size(); ++i)
    {
      if (_deps[i].filename->equals(filename) == true)
        return _deps[i];
    }
    return DepCacheEntry(Nil, 0);
  }
};

DepCache _depCache;

struct DepCacheStack
{
  ::acdk::lang::sys::core_vector<DepCacheEntry> _stack;
  typedef ::acdk::lang::sys::core_vector<DepCacheEntry>::iterator iterator;
  void push(const DepCacheEntry& dce)
  {
    _stack.push_back(dce);
  }
  void setMinTime()
  {
    
    jlong mintime = _stack.back().minmodtime;
    if (mintime != _stack.back().modtime)
      return;
    DOUT("Set MinTime: " << _stack.back().filename << " " << acdk::util::SysDate(mintime).toString());
    iterator it = _stack.begin();
    iterator end = _stack.end();
    for (; it < end; ++it)
    {
      it->setMinLastModified(mintime);
    }
  }
  DepCacheEntry& top()
  {
    return _stack.back();
  }
  DepCacheEntry pop()
  {
    DepCacheEntry ret = top();
    _stack.erase(_stack.end() - 1);
    return ret;
  }
};

struct DepCacheStackHolder
{
  DepCacheStack& _stack;
  DepCacheStackHolder(DepCacheStack& stack, IN(RString) filename)
  : _stack(stack)
  {
   acdk::io::FileStatus ss(filename);
   _stack.push(DepCacheEntry(filename, ss.lastModified()));
  }
  ~DepCacheStackHolder()
  {
    _stack.setMinTime();
    _depCache.add(_stack.pop());
  }
};

RString readInclude(StreamTokenizer& tin)
{
  StringBuffer sb("");
  int tk;
  while ((tk = tin.nextToken()) != StreamTokenizer::TT_EOF)
  {
    if (tk == StreamTokenizer::TT_WORD)
      sb.append(tin.sval);
    else if (tk == '/' || tk == '.')
      sb.append((char)tk);
    else if (tk == '>')
      return sb.toString();
    else
    {
      ACDK_NLOG("acdk.make", Warn, tin.getStreamPos() + ": Unexpected in #include < read_content >" + tin.lastReaded());  
      return Nil;
    }
  }
  return sb.toString();
}

bool 
CppSourceDependTask::parseFile(IN(RString) filename, jlong ts, IN(RStringArray) parsedFiles, IN(RString) cwd, DepCacheStack& dcs)
{
  
  File file(filename);
  if (file.exists() == false)
  {
    ACDK_NLOG("acdk.make", Note, "File doesn't exists: " + filename);
    return true;
  }
  RString fqfile = file.getCanonicalPath();
  if (useCache == false)
  {
    acdk::io::FileStatus ss(fqfile);
    if (ts - ss.lastModified() < 0)
    {
      ACDK_NLOG("acdk.make", Trace, 
          "Source is younger: " + filename +
          " Target: " + acdk::util::SysDate(ts).toString() +
          " Source: " + acdk::util::SysDate(ss.lastModified()).toString()
          );  
      return false;
    }
  }
  

  if (::acdk::util::Arrays::sequenceSearch(parsedFiles, fqfile) != -1)
    return true;
  if (useCache == true)
  {
    DepCacheEntry de = _depCache.find(fqfile);
    if (de.filename != Nil)
    {
      DOUT("DepCacheHit: " << fqfile);
      if (de.minmodtime > ts)
        return false;
      return true;
    }
  }
  ACDK_NLOG("acdk.make", Trace, "CppSourceDependTask: Parse File: " + fqfile);
  DOUT("ParseFile: " << fqfile);
  parsedFiles->append(fqfile);
  DepCacheStackHolder _dcsh(dcs, fqfile);

  acdk::io::FileReader fin(filename);
  acdk::io::ByteToCharReader chin(&fin, acdk::locale::Encoding::getEncoding("LATIN-1")->getDecoder());
  //ChDir cdir(file.getParent());
  RString ncwd = file.getParent();
  StreamTokenizer tin(&chin);
  int tk = 0;
  bool bret = true;
  while (true)
  {
    tk = tin.nextToken();
from_begining:
    if (tk == StreamTokenizer::TT_EOF)
      return true;

    if (tk == '#')
    {
      tk = tin.nextToken();
      if (tk == StreamTokenizer::TT_WORD && tin.sval->equals("include") == true)
      {
        tk = tin.nextToken();
        if (tk == '<') 
        {
          if (_checkOnlyDirectIncludes == true)
          {
            tin.skipLine();
            continue;
          }
          RString sfile = readInclude(tin);
          RString ffile;
          RString fdir;
          if (sfile != Nil)
          {
            File tf(ncwd, sfile);
            if (tf.exists() == true)
            {
              ffile = tf.getCanonicalPath();
              fdir = tf.getParent();
            } 
            else
            {
              for (int i = 0; i < _includeDirs->length(); ++i)
              {
                File f(_includeDirs[i], sfile);
                if (f.exists() == true)
                {
                  ffile = f.getCanonicalPath();
                  fdir = f.getParent();
                  break;
                }
              }
            }
            if (ffile != Nil)
            {
              if (File(ffile).exists() == false)
              {
                ACDK_NLOG("acdk.make", Trace, "in file " + filename + " included <File> doesn't exists: " + ffile);
              } 
              else 
              {

                bret &= parseFile(ffile, ts, parsedFiles, fdir, dcs);
                if (useCache == false && bret == false)
                  return false;
              }
            }
            else
            {
              ACDK_NLOG("acdk.make", Trace, "in file " + filename + " included <File> cannot be found: " + sfile);  
            }
            tin.skipLine();
            continue;
          }
        } 
        else if (tk == StreamTokenizer::TT_STRING)
        {
          File tf(ncwd, tin.sval);
          if (tf.exists() == false)
            ACDK_NLOG("acdk.make", Note, "in file " + filename + " included \"File\" doesn't exists: " + tin.sval);
          else 
          {
            RString ffile = tf.getCanonicalPath();
            RString fdir = tf.getParent();
            bret &= parseFile(ffile, ts, parsedFiles, fdir, dcs);
            if (useCache == false && bret == false)
              return false;
          }
          tin.skipLine();
          continue;
        }
      }
      else if (tk == StreamTokenizer::TT_WORD && tin.sval->equals("define") == true)
      {
      }
      else
      {
        tin.skipLine();
        continue;
      }
    } 
    else if (tk == StreamTokenizer::TT_WORD && tin.sval->equals("namespace") == true)
    {
      break;
    } 
    else 
    {
      tin.skipLine();
      continue;
    }
  }
  return bret;
}

//virtual 
bool 
CppSourceDependTask::execute(IN(RString) exec, IN(RProps) props)
{
  SYNCCLASS(); // cannot execute mutliple version because ChDir
  //DOUT("Parse: " << _target);
  File tf(_target);
  if (tf.exists() == false)
  {
    ACDK_NLOG("acdk.make", Note, "File doesn't exists: " + _target);
    return true;
  }
  jlong lastmod;
  {
    //RString fqfilename = tf.getCanonicalPath();
    ::acdk::io::FileStatus ts(_target);
    lastmod = ts.lastModified();
  }
  StringArray _parsedFiles(0);
  DepCacheStack dcs;
  File sf(_source);
  RString fdir = sf.getParent();
  RString ffname = sf.getName();
  return parseFile(_source, lastmod, &_parsedFiles, fdir, dcs);
}
  



} // namespace make
} // namespace acdk