On Thu, Apr 3, 2008 at 8:39 AM, Archie Cobbs <[EMAIL PROTECTED]> wrote:

> I think I have enough information to go and work on a new resolver based
> on these ideas... will report back later...
>

Here's a very rough first cut at a new "builder" resolver.

Here's the basic idea: it works like URLResolver for the ivy.xml file. For
other artifacts, it short-circuits the normal functioning: first, it gets
the build.xml file by finding the "builder" artifact (using normal
URLResolver stuff). This build.xml is just an ant file that is expected to
do whatever action necessary to "build" the artifacts and put them in an
artifacts/ subdirectory. This build.xml is executed by running "ant" as a
separate process. Then the resolver returns the artifacts found within that
artifacts/ subdirectory.

It includes code to clean up the work directory when the JVM exits.

Some things that may to be improved:

   1. Would it be better to invoke ant from within the same JVM? Actually
   I think not to avoid property pollution, etc. which might make the build
   process less robust and reproducible.
   2. Ivy is not caching the built artifacts for some reason (I think)
   3. It would be fancier to define custom XML for the build file and
   XSLT into a build.xml, but maybe not worth the trouble either.
   4. Need to add checksum verification to the build.xml file after it
   downloads the ZIP file.

To try this out:

   1. Apply the attached patch to your ivy source tree
   2. Run "ant roundup"
   3. You should eventually see the TestNG usage message printed out,
   proving that it worked

I'm sure there are other bugs at least the basic initial test works. Please
let me know what you think.

Thanks,
-Archie

-- 
Archie L. Cobbs
Index: src/java/org/apache/ivy/plugins/resolver/builder/BuildRoot.java
===================================================================
--- src/java/org/apache/ivy/plugins/resolver/builder/BuildRoot.java     
(revision 0)
+++ src/java/org/apache/ivy/plugins/resolver/builder/BuildRoot.java     
(revision 0)
@@ -0,0 +1,33 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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 org.apache.ivy.plugins.resolver.builder;
+
+/**
+ * Wrapper class for the configurator to set build directory.
+ */
+public class BuildRoot {
+    private String dir;
+
+    public String getDir() {
+        return this.dir;
+    }
+
+    public void setDir(String dir) {
+        this.dir = dir;
+    }
+}

Property changes on: 
src/java/org/apache/ivy/plugins/resolver/builder/BuildRoot.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Index: src/java/org/apache/ivy/plugins/resolver/builder/BuiltFileResource.java
===================================================================
--- src/java/org/apache/ivy/plugins/resolver/builder/BuiltFileResource.java     
(revision 0)
+++ src/java/org/apache/ivy/plugins/resolver/builder/BuiltFileResource.java     
(revision 0)
@@ -0,0 +1,85 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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 org.apache.ivy.plugins.resolver.builder;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.plugins.repository.Resource;
+
+/**
+ * Represents an artifact built by a [EMAIL PROTECTED] BuilderResolver}.
+ */
+public class BuiltFileResource implements Resource {
+
+    /**
+     * Where the build file should put built artifacts (relative
+     * to the build directory). Value is: [EMAIL PROTECTED]
+     */
+    public static final String BUILT_ARTIFACT_PATTERN = 
"artifacts/[artifact].[ext]";
+
+    private final File file;
+
+    public BuiltFileResource(File file) {
+        this.file = file;
+    }
+
+    public BuiltFileResource(File dir, Artifact artifact) {
+        this(new File(dir, IvyPatternHelper.substitute(BUILT_ARTIFACT_PATTERN, 
artifact)));
+    }
+
+    public String getName() {
+        return file.getPath();
+    }
+
+    public Resource clone(String name) {
+        return new BuiltFileResource(new File(name));
+    }
+
+    public long getLastModified() {
+        return file.lastModified();
+    }
+
+    public long getContentLength() {
+        return file.length();
+    }
+
+    public boolean exists() {
+        return file.exists();
+    }
+
+    public String toString() {
+        return getName();
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    public boolean isLocal() {
+        return true;
+    }
+
+    public InputStream openStream() throws IOException {
+        return new FileInputStream(file);
+    }
+}

Property changes on: 
src/java/org/apache/ivy/plugins/resolver/builder/BuiltFileResource.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Index: src/java/org/apache/ivy/plugins/resolver/builder/BuilderResolver.java
===================================================================
--- src/java/org/apache/ivy/plugins/resolver/builder/BuilderResolver.java       
(revision 0)
+++ src/java/org/apache/ivy/plugins/resolver/builder/BuilderResolver.java       
(revision 0)
@@ -0,0 +1,150 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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 org.apache.ivy.plugins.resolver.builder;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.resolver.URLResolver;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.apache.ivy.util.Message;
+
+/**
+ * Resolver that performs a "build" operation to resolve artifacts.
+ *
+ * <p>
+ * The resolver is configured with a base URL, from which the "ivy.xml"
+ * and "builder.xml" files are resolved. The latter file contains
+ * instructions describing how to build the actual artifacts.
+ */
+public class BuilderResolver extends URLResolver {
+
+    private static final String BUILDER_ARTIFACT_NAME = "builder";
+    private static final String BUILDER_ARTIFACT_TYPE = "builder";
+    private static final String BUILDER_ARTIFACT_EXT = "xml";
+
+    private final HashMap/*<ModuleRevisionId, BuilderCacheEntry>*/ 
builderCache = new HashMap();
+
+    private File buildRoot;
+
+    public BuilderResolver() {
+        Runtime.getRuntime().addShutdownHook(new Thread() {
+            public void run() {
+                clearCache();
+            }
+        });
+    }
+
+    protected synchronized void clearCache() {
+        for (Iterator i = builderCache.values().iterator(); i.hasNext();) {
+            BuilderCacheEntry entry = (BuilderCacheEntry) i.next();
+            entry.cleanup();
+        }
+        builderCache.clear();
+        this.buildRoot.delete();
+    }
+
+    /**
+     * Get root directory under which builds take place.
+     */
+    public String getBuildRoot() {
+        return this.buildRoot.toString();
+    }
+    public void setBuildRoot(String buildRoot) {
+        this.buildRoot = new File(buildRoot);
+    }
+
+    // For the ivy configurator
+    public void addConfiguredBuildRoot(BuildRoot buildRoot) {
+        setBuildRoot(buildRoot.getDir());
+    }
+
+    public void setAllownomd(boolean b) {
+        Message.error("allownomd not supported by resolver " + this);
+    }
+    public void setDescriptor(String rule) {
+        if (DESCRIPTOR_OPTIONAL.equals(rule)) {
+            Message.error("descriptor=\"" + DESCRIPTOR_OPTIONAL
+              + "\" not supported by resolver " + this);
+            return;
+        }
+        super.setDescriptor(rule);
+    }
+
+    // @Override
+    protected synchronized ResolvedResource findArtifactRef(Artifact artifact, 
Date date) {
+
+        // For our special build file, defer to superclass
+        if (BUILDER_ARTIFACT_NAME.equals(artifact.getName())
+          && BUILDER_ARTIFACT_TYPE.equals(artifact.getType())
+          && BUILDER_ARTIFACT_EXT.equals(artifact.getExt())) {
+            return super.findArtifactRef(artifact, date);
+        }
+
+        // Check the cache
+        ModuleRevisionId mr = artifact.getModuleRevisionId();
+        BuilderCacheEntry entry = (BuilderCacheEntry) builderCache.get(mr);
+
+        // Ignore invalid entries
+        if (entry != null && !entry.isBuilt()) {
+            builderCache.remove(mr);
+            entry.cleanup();
+            entry = null;
+        }
+
+        // Initialize build root
+        if (this.buildRoot == null) {
+            try {
+                this.buildRoot = File.createTempFile("IvyBuilderResolver_",
+                  "", new File(System.getProperty("java.io.tmpdir")));
+            } catch (IOException e) {
+                throw new RuntimeException("can't create build directory", e);
+            }
+        }
+
+        // Build the artifacts (if not done already)
+        if (entry == null) {
+            ResolvedResource builder = findArtifactRef(new DefaultArtifact(mr, 
null,
+              BUILDER_ARTIFACT_NAME, BUILDER_ARTIFACT_TYPE, 
BUILDER_ARTIFACT_EXT), date);
+            if (builder == null) {
+                return null;
+            }
+            entry = new BuilderCacheEntry(mr, this.buildRoot);
+            try {
+                entry.build(builder.getResource().openStream());
+            } catch (IOException e) {
+                throw new RuntimeException("can't build artifact " + artifact, 
e);
+            }
+            builderCache.put(mr, entry);
+        }
+
+        // Return reference to desired artifact
+        return entry.getBuiltArtifact(artifact);
+    }
+
+    public String getTypeName() {
+        return "builder";
+    }
+}
+

Property changes on: 
src/java/org/apache/ivy/plugins/resolver/builder/BuilderResolver.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Index: src/java/org/apache/ivy/plugins/resolver/builder/BuilderCacheEntry.java
===================================================================
--- src/java/org/apache/ivy/plugins/resolver/builder/BuilderCacheEntry.java     
(revision 0)
+++ src/java/org/apache/ivy/plugins/resolver/builder/BuilderCacheEntry.java     
(revision 0)
@@ -0,0 +1,180 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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 org.apache.ivy.plugins.resolver.builder;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+
+/**
+ * Represents one entry in a [EMAIL PROTECTED] BuilderCache}.
+ */
+public class BuilderCacheEntry {
+
+    private static final int BUFSIZE = 1024;
+
+    private final ModuleRevisionId mr;
+    private final File dir;
+    private boolean built;
+
+    public BuilderCacheEntry(ModuleRevisionId mr, File rootDir) {
+        this.mr = mr;
+        this.dir = getSubdir(rootDir, this.mr);
+    }
+
+    private static File getSubdir(File rootDir, ModuleRevisionId mr) {
+        return new File(rootDir,
+          mr.getOrganisation() + File.separatorChar
+          + mr.getName() + File.separatorChar
+          + mr.getRevision());
+    }
+
+    /**
+     * Attempt to build this entry.
+     *
+     * @param buildFile an ant build file
+     * @throws IllegalStateException if this entry has already been built.
+     */
+    public synchronized void build(InputStream buildFile) throws IOException {
+
+        // Sanity check
+        if (this.built) {
+            throw new IllegalStateException("build in directory `"
+              + this.dir + "' already completed");
+        }
+
+        // Create work directory
+        if (!this.dir.mkdirs()) {
+            throw new IOException("can't create directory `" + this.dir + "'");
+        }
+        this.dir.deleteOnExit();
+
+        // Write out build file
+        OutputStream out = new BufferedOutputStream(
+          new FileOutputStream(new File(dir, "build.xml")));
+        copy(buildFile, out);
+        buildFile.close();
+        out.close();
+
+        // Run ant
+        Process proc = Runtime.getRuntime().exec("ant", null, this.dir);
+        proc.getOutputStream().close();
+        Thread relay1 = startRelay(proc.getInputStream(), System.out);
+        Thread relay2 = startRelay(proc.getErrorStream(), System.err);
+        int result;
+        try {
+            relay1.join();
+            relay2.join();
+            result = proc.waitFor();
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        if (result != 0) {
+            throw new IOException("build in directory `" + this.dir + "' 
failed");
+        }
+        this.built = true;
+    }
+
+    /**
+     * Has this entry been successfully built?
+     */
+    public synchronized boolean isBuilt() {
+        return this.built;
+    }
+
+    /**
+     * Get a built artifact.
+     *
+     * @throws IllegalStateException if this entry's built has not
+     *  (yet) completed successfully
+     */
+    public ResolvedResource getBuiltArtifact(Artifact artifact) {
+        if (!this.built) {
+            throw new IllegalStateException("build in directory `" + this.dir
+              + "' has not yet successfully completed");
+        }
+        return new ResolvedResource(
+          new BuiltFileResource(this.dir, artifact), this.mr.getRevision());
+    }
+
+    public synchronized void cleanup() {
+        this.built = false;
+        deleteRecursive(this.dir);
+    }
+
+    public boolean deleteRecursive(File file) {
+        if (file.isDirectory()) {
+            File[] files = file.listFiles();
+            for (int i = 0; i < files.length; i++) {
+                if (!deleteRecursive(files[i])) {
+                    return false;
+                }
+            }
+        }
+        return file.delete();
+    }
+
+    /**
+     * Create a separate thread that copies input to output.
+     */
+    protected Thread startRelay(final InputStream in, final OutputStream out) {
+        Thread thread = new Thread() {
+            public void run() {
+                try {
+                    copy(in, out);
+                    in.close();
+                    out.close();
+                } catch (IOException e) {
+                    return;
+                }
+            }
+        };
+        thread.setDaemon(true);
+        thread.start();
+        return thread;
+    }
+
+    /**
+     * Copy from input to output. Does not close either stream when finished.
+     */
+    protected void copy(InputStream in, OutputStream out) throws IOException {
+        byte[] buf = new byte[BUFSIZE];
+        int r;
+        while ((r = in.read(buf)) != -1) {
+            out.write(buf, 0, r);
+        }
+        out.flush();
+    }
+
+    // @Override
+    protected void finalize() throws Throwable {
+        try {
+            cleanup();
+        } finally {
+            super.finalize();
+        }
+    }
+}
+

Property changes on: 
src/java/org/apache/ivy/plugins/resolver/builder/BuilderCacheEntry.java
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Index: src/java/org/apache/ivy/core/settings/typedef.properties
===================================================================
--- src/java/org/apache/ivy/core/settings/typedef.properties    (revision 
645018)
+++ src/java/org/apache/ivy/core/settings/typedef.properties    (working copy)
@@ -27,6 +27,7 @@
 vsftp       = org.apache.ivy.plugins.resolver.VsftpResolver
 vfs         = org.apache.ivy.plugins.resolver.VfsResolver
 cache          = org.apache.ivy.plugins.resolver.CacheResolver
+builder                = 
org.apache.ivy.plugins.resolver.builder.BuilderResolver
 
 latest-revision = org.apache.ivy.plugins.latest.LatestRevisionStrategy
 latest-lexico   = org.apache.ivy.plugins.latest.LatestLexicographicStrategy
Index: roundup-settings.xml
===================================================================
--- roundup-settings.xml        (revision 0)
+++ roundup-settings.xml        (revision 0)
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+<!--
+    Test of the BuilderResolver
+-->
+
+<!-- $Id$ -->
+<ivysettings>
+    <resolvers>
+        <builder name="roundup" validate="true">
+
+            <!-- This defines the Ivy file location as is normally done -->
+            <ivy 
pattern="http://people.freebsd.org/~archie/roundup/[organisation]/[module]/[revision]/ivy.xml"/>
+
+            <!-- This defines the location of the build instructions (in the 
form of an ant build file) -->
+            <artifact 
pattern="http://people.freebsd.org/~archie/roundup/[organisation]/[module]/[revision]/build.xml"/>
+
+            <!-- This defines the root directory under which we perform the 
builds -->
+            <buildRoot dir="${java.io.tmpdir}/roundup-builds"/>
+        </builder>
+    </resolvers>
+    <modules>
+        <module organisation="dellroad" name=".*" resolver="roundup"/>
+    </modules>
+    <caches useOrigin="true"/>
+</ivysettings>
+

Property changes on: roundup-settings.xml
___________________________________________________________________
Name: svn:mime-type
   + text/xml
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Index: build.xml
===================================================================
--- build.xml   (revision 645018)
+++ build.xml   (working copy)
@@ -568,7 +568,26 @@
         </xslt>
     </target>
     
+    <!-- =================================================================
+         IVY ROUNDUP TEST
+         ================================================================= -->
+    <target name="roundup" depends="jar">
 
+        <!-- Configure Ivy -->
+        <ivy:settings id="roundup-settings" override="true" 
file="roundup-settings.xml"/>
+
+        <!-- Find TestNG (dynamic revision) using roundup resolver -->
+        <ivy:resolve revision="5.7+" settingsRef="roundup-settings" type="jar"
+          organisation="dellroad" module="testng" inline="true" 
log="download-only"/>
+        <ivy:cachepath pathid="testng.classpath" 
settingsRef="roundup-settings" type="jar"
+          organisation="dellroad" module="testng" inline="true" 
log="download-only"/>
+
+        <!-- Run TestNG's main class causing it to output usage message -->
+        <java classname="org.testng.TestNG" fork="true">
+            <classpath refid="testng.classpath"/>
+        </java>
+    </target>
+
     <!-- =================================================================
          IDE SPECIFIC
          ================================================================= -->
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to