I've been following Nant for some time now and just wanted to contribute
some code
to the project. It's something that I've been using at work to do part of our
mechanized builds (Tivoli packages) for two different projects. Anyway,
for those
with Tivoli experience, you simply can't put everything into a single
fileset, you
really need to 'merge' a few different directories and prefix things. This was
one of the reasons why Ant implemented zipfilesets. So here it is, a basic
implementation
of the zipfileset tag as specified in the Ant doc's
(http://jakarta.apache.org/ant/manual/CoreTasks/zip.html). Implemented:
* includes / excludes facility as implemented in filesets (simply wrapped)
* property substitutions supported
* prefix's supported
* dir supported (similar to basedir but sports a different function)
* networks paths supported
A common setup could be of the following:
<zip zipfile="C:\package\dist_2002.zip" ziplevel="9">
<fileset>
<includes basedir="d:\" name="*.build" />
</fileset>
<zipfileset dir="${homeDir}" includes="*.log" prefix="info" />
<zipfileset dir="C:\Inetpub\wwwroot\wsQuotingFacility\" includes="**"
prefix="wsQuotingFacility" />
<zipfileset dir="C:\Inetpub\wwwroot\StaticQuotePages" includes="**"
prefix="StaticQuotePages" />
<zipfileset dir="\\bbstip3\projects$\eCommerce\Project Documents"
includes="**.doc" excludes="temp.doc" prefix="docs" />
</zip>
The zip archive dist_2002.zip would look like (if run from build directory):
build\test.build
info\change.log
info\history.log
wsQuotingFacility\test.asmx
wsQuotingFacility\web.config
StaticQuotePages\entry.html
StaticQuotePages\login.html
StaticQuotePages\includes\formatting.js
StaticQuotePages\includes\header.html
StaticQuotePages\includes\footer.html
docs\requirements.doc
docs\revised\requirements.doc
The code basically was tested with the latest CVS snapshot as of 8/13/2002
9:00PM
and it's backwards compatible with the current zip task (always a good
thing). Did
have to do some refactoring (to pull some commonly shared code with fileset
& zipfileset).
Besides, ExecuteTask looks much cleaner now.... I'm still putting together
a 'polished'
set of NUnit test cases that can be contributed, but I just wanted to get a
feel for
what people thought.
One thing that I have noticed while working on this task is that the
SharpZipLib
currently requires the "file to be compressed" to be loaded into memory
first. This is
okay for small files but would cause issues for larger files (i.e. system
would be required
to page swap a lot more). I'm wondering, as I haven't researched it,
whether SharpZipLib
has plans to allow streamed input. Since compression usually works on 64Kb
'data' windows,
or 128Kb if you're using UltraCompressor - http://www.xs4all.nl/~aipnl/,
you really don't
have to load the entire file into memory at once.
Enjoy!
-Wally
---------------------------------------------------------------------------------------------------
// NAnt - A .NET build tool
// Copyright (C) 2001 Gerry Shaw
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// Mike Krueger ([EMAIL PROTECTED])
// Gerry Shaw ([EMAIL PROTECTED])
using System;
using System.Collections;
using System.Collections.Specialized;
using System.IO;
using System.Xml;
using SourceForge.NAnt.Attributes;
using ICSharpCode.SharpZipLib.Checksums;
using ICSharpCode.SharpZipLib.Zip;
namespace SourceForge.NAnt.Tasks {
/// <summary>
/// A task to create a zip file from a specified fileset.
/// </summary>
/// <remarks>
/// <para>Uses <a
href="http://www.icsharpcode.net/OpenSource/NZipLib/">NZipLib</a>, an open
source Zip/GZip library written entirely in C#.</para>
/// </remarks>
/// <example>
/// <para>Zip all files in the subdirectory <c>build</c> to
<c>backup.zip</c>.</para>
/// <code>
/// <![CDATA[
/// <zip zipfile="backup.zip">
/// <fileset basedir="build">
/// <includes name="*.*"/>
/// </fileset>
/// </zip>
/// ]]>
/// </code>
/// </example>
[TaskName("zip")]
public class ZipTask : Task {
string _zipfile = null;
int _ziplevel = 6;
FileSet _fileset = new FileSet();
Crc32 crc = new Crc32();
ArrayList _zipFileSetGroup=new ArrayList();
/// <summary>The zip file to create.</summary>
[TaskAttribute("zipfile", Required=true)]
public string ZipFileName { get { return
Project.GetFullPath(_zipfile); } set {_zipfile = value; } }
/// <summary>Desired level of compression (default is 6).</summary>
/// <value>0 - 9 (0 - STORE only, 1-9 DEFLATE (1-lowest,
9-highest))</value>
[TaskAttribute("ziplevel", Required=false)]
public int ZipLevel { get { return _ziplevel; } set
{_ziplevel = value; } }
/// <summary>The set of files to be included in the archive.</summary>
[FileSet("fileset")]
public FileSet ZipFileSet { get { return _fileset; } }
protected override void ExecuteTask() {
ZipOutputStream zOutstream = new
ZipOutputStream(File.Create(ZipFileName));
// set the compression level
int zipLevel = ZipLevel;
if (zipLevel > 0) {
zOutstream.SetLevel(zipLevel);
}
else {
zOutstream.SetMethod(ZipOutputStream.STORED);
}
AppendFilesFromFileSetToZipArchive(ref zOutstream);
AppendFilesFromZipFileSetsToZipArchive(ref zOutstream);
zOutstream.Finish();
zOutstream.Close();
}
private String RemoveLeadingPath(string leadPath,string path) {
if(null==path) return null; // avoid 'null' scenarios up-front
if(path.StartsWith(leadPath)) {
path=path.Substring(leadPath.Length);
}
// we strip any leading seperator char's as they tend to arise
// inappropriately for network paths, not sure if this is a
'feature'
// or a special case of the Path.GetPathRoot functionality
while(path[0]==Path.DirectorySeparatorChar) {
path=path.Substring(1);
}
return path;
}
/// <summary>
/// Replace the leading part of the full file path with the prefix.
/// we spend effort to remove leading directory seperators from the
'fn' so that
/// the zip archive doesn't contain any of them in the leading position
/// For example, we want the archive to have
/// dir123\file.dat
/// Not
/// \dir123\file.dat
/// </summary>
/// <param name="startDir"></param>
/// <param name="prefix"></param>
/// <param name="fn"></param>
/// <returns></returns>
private String ReplaceFullFilePathWithPrefix(string startDir, string
prefix, string fn)
{
string rootPath=Path.GetPathRoot(fn);
// strip rootPath from startDir & fn
startDir=RemoveLeadingPath(rootPath,startDir);
fn=RemoveLeadingPath(rootPath,fn);
if(0<prefix.Length) {
fn=RemoveLeadingPath(startDir,fn);
return Path.Combine(prefix,fn);
}
return fn;
}
private void AppendFilesFromFileSetToZipArchive(ref ZipOutputStream
zOutstream) {
if(Verbose) {
int fileCount=ZipFileSet.FileNames.Count;
string fileText=(1==fileCount) ? "file" : "files";
Log.WriteLine(LogPrefix + "Zipping {0} {1} to {2}",
ZipFileSet.FileNames.Count, fileText, ZipFileName);
}
string basePath =
Path.GetDirectoryName(Project.GetFullPath(ZipFileSet.BaseDirectory));
foreach (string file in ZipFileSet.FileNames) {
string entryName = file.Substring(basePath.Length + 1);
ZipFileUp(ref zOutstream, file, entryName);
}
}
private void AppendFilesFromZipFileSetsToZipArchive(ref
ZipOutputStream zOutstream) {
foreach(ZipFileSetElement zfs in _zipFileSetGroup) {
string
filePath=Path.GetDirectoryName(Project.GetFullPath(zfs.Dir+Path.DirectorySeparatorChar));
FileSet fs=new FileSet();
fs.BaseDirectory=filePath;
fs.Includes.Add(zfs.IncludesPattern);
fs.DefaultExcludes=false;
fs.Excludes.Add(zfs.ExcludesPattern);
if(Verbose) {
int fileCount=fs.FileNames.Count;
string fileText=(1==fileCount) ? "file" : "files";
Log.WriteLine(LogPrefix + "Zipping {0} {1} to
{2}",
ZipFileSet.FileNames.Count, fileText, ZipFileName);
}
int i=0;
foreach(String fn in fs.FileNames) {
string
zipEntryName=ReplaceFullFilePathWithPrefix(fs.BaseDirectory,zfs.Prefix,fn);
ZipFileUp(ref zOutstream, fn, zipEntryName);
if(++i>5) break;
}
}
}
/// <summary>
/// NOTE: Since we are reading in the entire file into memory and then
compressing it,
/// we will be incurring a memory penalty (especially in terms of
memory allocation/
/// garbage collection). What would be better is to pass the data
stream to
/// the NZipLib, however, currently that library doesn't handle
this. Their is also
/// a performance limitation on larger files, which have to be loaded
into memory all
/// at once.
/// NOTE: Would it make sense to research some sort of mechanism to
zip-up files that
/// are currently locked by applications? For example, my need is to
have the Visual
/// Studio project to be archived, I don't want to quit my project to
be able to zip
/// things up.
/// </summary>
/// <param name="zOutstream"></param>
/// <param name="fnOnDisk"></param>
/// <param name="zipEntryName"></param>
private void ZipFileUp(ref ZipOutputStream zOutstream, string
fnOnDisk, string zipEntryName) {
if (!File.Exists(fnOnDisk)) {
throw new FileNotFoundException();
}
// read source file
FileStream fStream = File.OpenRead(fnOnDisk);
long fileSize = fStream.Length;
byte[] buffer = new byte[fileSize];
fStream.Read(buffer, 0, buffer.Length);
fStream.Close();
// create ZIP entry
ZipEntry entry = new ZipEntry(zipEntryName);
if (Verbose) {
Log.WriteLine(LogPrefix + " Adding {0}", zipEntryName);
}
if (ZipLevel == 0) {
entry.Size = fileSize;
// calculate crc32 of current file
crc.Reset();
crc.Update(buffer);
entry.Crc = crc.Value;
}
// write file to ZIP
zOutstream.PutNextEntry(entry);
zOutstream.Write(buffer, 0, buffer.Length);
}
protected override void InitializeElement(XmlNode elementNode) {
// The ZipFileSet class will initialize the marked xml attributes
but
// not the unmarked sub-elements. We don't have any for
ZipFileSet yet,
// but they could go here. Alternatively we could have built the
// group-of-ZipFileSet from an overridden InitializeTask.
foreach (XmlNode node in elementNode) {
if("zipfileset".Equals(node.Name)) {
ZipFileSetElement zfs = new ZipFileSetElement();
zfs.Project=Project;
zfs.Initialize(node);
_zipFileSetGroup.Add(zfs);
}
}
}
[ElementName("zipfileset")]
private class ZipFileSetElement : Element
{
private string _dir="";
private string _prefix="";
private string _includesPattern="";
private string _excludesPattern="";
[TaskAttribute("prefix", Required=false)]
public string Prefix
{
get { return _prefix; }
set { _prefix=value;}
}
[TaskAttribute("dir", Required=false)]
public string Dir
{
get { return _dir; }
set { _dir=value; }
}
[TaskAttribute("includes", Required=false)]
public string IncludesPattern
{
get { return _includesPattern; }
set { _includesPattern=value;}
}
[TaskAttribute("excludes", Required=false)]
public string ExcludesPattern
{
get { return _excludesPattern; }
set { _excludesPattern=value;}
}
}
}
}
-------------------------------------------------------
This sf.net email is sponsored by: Dice - The leading online job board
for high-tech professionals. Search and apply for tech jobs today!
http://seeker.dice.com/seeker.epl?rel_code=31
_______________________________________________
Nant-developers mailing list
[EMAIL PROTECTED]
https://lists.sourceforge.net/lists/listinfo/nant-developers