Adding BuckLibGenerator to autogenerate lib/BUCK

Change-Id: I7eab26f57d4f5886b1512b687fca75d684938d46
diff --git a/lib/BUCK b/lib/BUCK
index 78f7c75..c0e7ca5 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -1,4 +1,4 @@
-# ***** This file was auto-generated at Mon May 02 11:42:36 PDT 2016. Do not edit this file manually. *****
+# ***** This file was auto-generated at Wed May 04 19:19:10 PDT 2016. Do not edit this file manually. *****
 osgi_feature_group(
   name = 'COMPILE',
   visibility = ['PUBLIC'],
@@ -187,9 +187,9 @@
 
 remote_jar (
   name = 'onos-atomix',
-  out = 'atomix-1.0.onos-20160502.173651-190.jar',
-  url = 'https://oss.sonatype.org/content/repositories/snapshots/org/onosproject/atomix/1.0.onos-SNAPSHOT/atomix-1.0.onos-20160502.173651-190.jar',
-  sha1 = '763da0295d060271ae223db474dd024a7e84c629',
+  out = 'atomix-1.0.onos-20160505.013719-220.jar',
+  url = 'https://oss.sonatype.org/content/repositories/snapshots/org/onosproject/atomix/1.0.onos-SNAPSHOT/atomix-1.0.onos-20160505.013719-220.jar',
+  sha1 = '9dabad079e1fcd15f58cf995b84eda562b8de21e',
   maven_coords = 'org.onosproject:atomix:1.0.onos-SNAPSHOT',
   visibility = [ 'PUBLIC' ],
 )
diff --git a/tools/build/libgen/BUCK b/tools/build/libgen/BUCK
new file mode 100644
index 0000000..60a1cb8
--- /dev/null
+++ b/tools/build/libgen/BUCK
@@ -0,0 +1,17 @@
+#FIXME build this dynamically
+
+URL = 'https://oss.sonatype.org/content/repositories/snapshots/org/onosproject/libgen/1.0-SNAPSHOT/libgen-1.0-20160505.022015-2.jar'
+SHA = 'fa29f6f5432587df65e55a7d0c99d1454577dcfd'
+
+prebuilt_jar(
+  name = 'libgen',
+  binary_jar = ':libgen-jar',
+  visibility = [ 'PUBLIC' ],
+)
+
+remote_file (
+  name = 'libgen-jar',
+  out = 'libgen.jar',
+  url = URL,
+  sha1 = SHA,
+)
\ No newline at end of file
diff --git a/tools/build/libgen/pom.xml b/tools/build/libgen/pom.xml
new file mode 100644
index 0000000..af3936f
--- /dev/null
+++ b/tools/build/libgen/pom.xml
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2016-present Open Networking Laboratory
+  ~
+  ~ 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.onosproject</groupId>
+        <artifactId>onos-base</artifactId>
+        <version>1</version>
+        <relativePath/>
+    </parent>
+
+    <groupId>org.onosproject</groupId>
+    <artifactId>libgen</artifactId>
+    <packaging>jar</packaging>
+    <version>1.0-SNAPSHOT</version>
+
+    <description>Buck third-party library generator</description>
+    <url>http://onosproject.org/</url>
+
+    <scm>
+        <connection>scm:git:https://gerrit.onosproject.org/onos</connection>
+        <developerConnection>scm:git:https://gerrit.onosproject.org/onos</developerConnection>
+        <url>http://gerrit.onosproject.org/</url>
+    </scm>
+
+    <licenses>
+        <license>
+            <name>Apache License, Version 2.0</name>
+            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+        </license>
+    </licenses>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+            <version>2.7.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>2.7.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.aether</groupId>
+            <artifactId>aether-api</artifactId>
+            <version>1.1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.aether</groupId>
+            <artifactId>aether-util</artifactId>
+            <version>1.1.0</version>
+        </dependency>
+        <!--<dependency>-->
+            <!--<groupId>commons-configuration</groupId>-->
+            <!--<artifactId>commons-configuration</artifactId>-->
+            <!--<version>1.10</version>-->
+        <!--</dependency>-->
+        <!--<dependency>-->
+            <!--<groupId>commons-collections</groupId>-->
+            <!--<artifactId>commons-collections</artifactId>-->
+            <!--<version>3.2.2</version>-->
+        <!--</dependency>-->
+        <dependency>
+            <groupId>org.apache.maven</groupId>
+            <artifactId>maven-aether-provider</artifactId>
+            <version>3.3.9</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.aether</groupId>
+            <artifactId>aether-connector-basic</artifactId>
+            <version>1.1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.aether</groupId>
+            <artifactId>aether-transport-file</artifactId>
+            <version>1.1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.aether</groupId>
+            <artifactId>aether-transport-http</artifactId>
+            <version>1.1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>1.6.2</version>
+            <scope>runtime</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>2.5.1</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <version>2.4.2</version>
+                <configuration>
+                    <transformers>
+                        <transformer
+                                implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+                            <mainClass>org.onosproject.libgen.BuckLibGenerator</mainClass>
+                        </transformer>
+                    </transformers>
+                    <filters>
+                        <filter>
+                            <artifact>*</artifact>
+                        </filter>
+                    </filters>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <version>2.10</version>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/tools/build/libgen/src/main/java/org/onosproject/libgen/AetherResolver.java b/tools/build/libgen/src/main/java/org/onosproject/libgen/AetherResolver.java
new file mode 100644
index 0000000..fbc3162
--- /dev/null
+++ b/tools/build/libgen/src/main/java/org/onosproject/libgen/AetherResolver.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * 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.
+ */
+package org.onosproject.libgen;
+
+import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.collection.CollectRequest;
+import org.eclipse.aether.collection.CollectResult;
+import org.eclipse.aether.collection.DependencyCollectionException;
+import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
+import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.graph.DependencyFilter;
+import org.eclipse.aether.graph.DependencyNode;
+import org.eclipse.aether.impl.DefaultServiceLocator;
+import org.eclipse.aether.repository.LocalRepository;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.repository.RepositoryPolicy;
+import org.eclipse.aether.resolution.ArtifactRequest;
+import org.eclipse.aether.resolution.ArtifactResult;
+import org.eclipse.aether.resolution.DependencyRequest;
+import org.eclipse.aether.resolution.VersionRangeRequest;
+import org.eclipse.aether.resolution.VersionRangeResolutionException;
+import org.eclipse.aether.resolution.VersionRangeResult;
+import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
+import org.eclipse.aether.spi.connector.transport.TransporterFactory;
+import org.eclipse.aether.transport.file.FileTransporterFactory;
+import org.eclipse.aether.transport.http.HttpTransporterFactory;
+import org.eclipse.aether.util.artifact.JavaScopes;
+import org.eclipse.aether.util.filter.DependencyFilterUtils;
+import org.eclipse.aether.version.Version;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.Reader;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+
+import static org.eclipse.aether.repository.RepositoryPolicy.CHECKSUM_POLICY_WARN;
+import static org.eclipse.aether.repository.RepositoryPolicy.UPDATE_POLICY_ALWAYS;
+
+/**
+ * Resolver capable of resolving Maven coordinates to a Maven artifact.
+ */
+public class AetherResolver {
+    private static final String CENTRAL_URL = "http://repo1.maven.org/maven2/";
+
+    private static RepositorySystem system;
+    private static RepositorySystemSession session;
+    private static final RemoteRepository CENTRAL =
+            new RemoteRepository.Builder("central", "default", CENTRAL_URL).build();
+
+    private final String repoUrl;
+
+    static {
+        DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
+        locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class );
+        locator.addService(TransporterFactory.class, FileTransporterFactory.class );
+        locator.addService(TransporterFactory.class, HttpTransporterFactory.class );
+
+        locator.setErrorHandler( new DefaultServiceLocator.ErrorHandler()
+        {
+            @Override
+            public void serviceCreationFailed( Class<?> type, Class<?> impl, Throwable exception )
+            {
+                exception.printStackTrace();
+            }
+        } );
+
+        AetherResolver.system = locator.getService( RepositorySystem.class );
+
+        DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
+
+        LocalRepository localRepo = new LocalRepository("target/local-repo" );
+        session.setLocalRepositoryManager( system.newLocalRepositoryManager( session, localRepo ) );
+
+        //session.setTransferListener( new ConsoleTransferListener() );
+        //session.setRepositoryListener( new ConsoleRepositoryListener() );
+        AetherResolver.session = session;
+    }
+
+    public static BuckArtifact getArtifact(String name, String uri, String repo) {
+        return new AetherResolver(repo).build(name, uri);
+    }
+
+    private AetherResolver(String repoUrl) {
+        this.repoUrl = repoUrl;
+    }
+
+    private BuckArtifact build(String name, String uri) {
+        uri = uri.replaceFirst("mvn:", "");
+        Artifact artifact = new DefaultArtifact(uri);
+        String originalVersion = artifact.getVersion();
+        try {
+            artifact = artifact.setVersion(newestVersion(artifact));
+            artifact = resolveArtifact(artifact);
+            String sha = getSha(artifact);
+            boolean osgiReady = isOsgiReady(artifact);
+
+            if (originalVersion.endsWith("-SNAPSHOT")) {
+                String url = String.format("%s/%s/%s/%s/%s-%s.%s",
+                                            repoUrl,
+                                            artifact.getGroupId().replace('.', '/'),
+                                            artifact.getArtifactId(),
+                                            originalVersion,
+                                            artifact.getArtifactId(),
+                                            artifact.getVersion(),
+                                            artifact.getExtension());
+                String mavenCoords = String.format("%s:%s:%s",
+                                                   artifact.getGroupId(),
+                                                   artifact.getArtifactId(),
+                                                   originalVersion);
+                return BuckArtifact.getArtifact(name, url, sha, mavenCoords, osgiReady);
+            }
+            return BuckArtifact.getArtifact(name, artifact, sha, repoUrl, osgiReady);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private Artifact resolveArtifact(Artifact artifact) throws Exception {
+        ArtifactRequest request = new ArtifactRequest();
+        request.setArtifact(artifact);
+        request.setRepositories(repositories());
+        ArtifactResult result = system.resolveArtifact(session, request);
+        return result.getArtifact();
+    }
+
+    private boolean isOsgiReady(Artifact artifact) throws Exception {
+        JarFile jar = new JarFile(artifact.getFile());
+        Attributes attrs = jar.getManifest().getMainAttributes();
+        return attrs.getValue("Bundle-SymbolicName") != null &&
+                attrs.getValue("Bundle-Version") != null;
+    }
+
+    private String getSha(Artifact artifact) throws Exception {
+        String directory = artifact.getFile().getParent();
+        String file = String.format("%s-%s.%s.sha1",
+                                    artifact.getArtifactId(),
+                                    artifact.getVersion(),
+                                    artifact.getExtension());
+        String shaPath = Paths.get(directory, file).toString();
+
+        try (Reader reader = new FileReader(shaPath)) {
+            return new BufferedReader(reader).readLine().trim().split(" ", 2)[0];
+        }
+    }
+
+    private String newestVersion(Artifact artifact) throws VersionRangeResolutionException {
+        VersionRangeRequest rangeRequest = new VersionRangeRequest();
+        rangeRequest.setArtifact(artifact);
+        rangeRequest.setRepositories(repositories());
+
+        VersionRangeResult rangeResult = system.resolveVersionRange(session, rangeRequest );
+
+        Version newestVersion = rangeResult.getHighestVersion();
+
+        return newestVersion.toString();
+    }
+
+    public List<RemoteRepository> repositories()
+    {
+        if (repoUrl != null && repoUrl.length() > 0) {
+            RepositoryPolicy policy = new RepositoryPolicy(true,
+                                                           UPDATE_POLICY_ALWAYS,
+                                                           CHECKSUM_POLICY_WARN);
+            RemoteRepository repository =
+                    new RemoteRepository.Builder("temp", "default", repoUrl)
+                            .setSnapshotPolicy(policy).build();
+            return Arrays.asList(CENTRAL, repository);
+        }
+
+        return Collections.singletonList(CENTRAL);
+    }
+}
diff --git a/tools/build/libgen/src/main/java/org/onosproject/libgen/BuckArtifact.java b/tools/build/libgen/src/main/java/org/onosproject/libgen/BuckArtifact.java
new file mode 100644
index 0000000..c9e9e19
--- /dev/null
+++ b/tools/build/libgen/src/main/java/org/onosproject/libgen/BuckArtifact.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * 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.
+ */
+package org.onosproject.libgen;
+
+import org.eclipse.aether.artifact.Artifact;
+
+/**
+ * Representation of a remote artifact for Buck.
+ */
+public abstract class BuckArtifact {
+
+    private final String name;
+    private final String sha;
+    private final boolean osgiReady;
+
+    public static BuckArtifact getArtifact(String name, Artifact artifact, String sha, String repo, boolean osgiReady) {
+        return new MavenArtifact(name, artifact, sha, repo, osgiReady);
+    }
+    public static BuckArtifact getArtifact(String name, String url, String sha, String mavenCoords, boolean osgiReady) {
+        return new HTTPArtifact(name, url, sha, mavenCoords, osgiReady);
+    }
+    public static BuckArtifact getArtifact(String name, String url, String sha) {
+        return new HTTPArtifact(name, url, sha, null, true);
+    }
+
+    public BuckArtifact(String name, String sha, boolean osgiReady) {
+        this.name = name;
+        this.sha = sha;
+        this.osgiReady = osgiReady;
+    }
+
+    public String name() {
+        return name;
+    }
+
+    abstract String fileName();
+
+    abstract String url();
+
+    private String jarTarget() {
+        return name != null ? name : fileName();
+    }
+
+    private boolean isPublic() {
+        return name != null;
+    }
+
+    boolean isOsgiReady() {
+        return osgiReady;
+    }
+
+    String mavenCoords() {
+        return null;
+    }
+
+    public String getBuckFragment() {
+        String visibility = isPublic() ? "[ 'PUBLIC' ]" : "[]";
+
+        boolean isJar = fileName().endsWith(".jar");
+        String output = (isJar ? "remote_jar" : "remote_file") + " (\n" +
+                "  name = '%s',\n" + // jar target
+                "  out = '%s',\n" + // jar file name
+                "  url = '%s',\n" + // maven url
+                "  sha1 = '%s',\n" + // artifact sha
+                ( isJar && mavenCoords() != null ?
+                "  maven_coords = '"+ mavenCoords()+"',\n" : "" ) +
+                "  visibility = %s,\n" +
+                ")\n\n";
+
+        return String.format(output, jarTarget(), fileName(), url(), sha, visibility);
+    }
+
+    private static class HTTPArtifact extends BuckArtifact {
+        private final String url;
+        private final String mavenCoords;
+
+        public HTTPArtifact(String name, String url, String sha, String mavenCoords, boolean osgiReady) {
+            super(name, sha, osgiReady);
+            this.url = url;
+            this.mavenCoords = mavenCoords;
+        }
+
+        @Override
+        String fileName() {
+            String[] parts = url.split("/");
+            return parts[parts.length - 1];
+        }
+
+        @Override
+        String mavenCoords() {
+            return mavenCoords;
+        }
+
+        @Override
+        String url() {
+            return url;
+        }
+    }
+
+    private static class MavenArtifact extends BuckArtifact {
+        private final Artifact artifact;
+        private final String repo;
+
+        private MavenArtifact(String name, Artifact artifact, String sha, String repo, boolean osgiReady) {
+            super(name, sha, osgiReady);
+            this.artifact = artifact;
+            this.repo = repo;
+        }
+
+        @Override
+        String url() {
+            //mvn:[repo:]groupId:artifactId:[extension:[classifier:]]:version
+            StringBuilder mvnUrl = new StringBuilder("mvn:");
+            if (repo != null && repo.length() > 0) {
+                mvnUrl.append(repo).append(':');
+            }
+            mvnUrl.append(artifact.getGroupId()).append(':')
+                  .append(artifact.getArtifactId()).append(':')
+                  .append(artifact.getExtension()).append(':');
+            if (artifact.getClassifier() != null && artifact.getClassifier().length() > 0) {
+                mvnUrl.append(artifact.getClassifier()).append(':');
+            }
+            mvnUrl.append(artifact.getVersion());
+            return mvnUrl.toString();
+        }
+
+        //FIXME get sources jars
+
+        @Override
+        String mavenCoords() {
+            String classifer = artifact.getClassifier();
+            if (!isOsgiReady()) {
+                classifer = "NON-OSGI" + classifer;
+            }
+
+            if ("jar".equals(artifact.getExtension().toLowerCase()) &&
+                    classifer.length() == 0) {
+                // shorter form
+                return String.format("%s:%s:%s",
+                                     artifact.getGroupId(),
+                                     artifact.getArtifactId(),
+                                     artifact.getVersion());
+            }
+            return String.format("%s:%s:%s:%s:%s",
+                                 artifact.getGroupId(),
+                                 artifact.getArtifactId(),
+                                 artifact.getExtension(),
+                                 classifer,
+                                 artifact.getVersion());
+        }
+
+        @Override
+        String fileName() {
+            return String.format("%s-%s.%s",
+                                 artifact.getArtifactId(),
+                                 artifact.getVersion(),
+                                 artifact.getExtension());
+        }
+    }
+}
diff --git a/tools/build/libgen/src/main/java/org/onosproject/libgen/BuckLibGenerator.java b/tools/build/libgen/src/main/java/org/onosproject/libgen/BuckLibGenerator.java
new file mode 100644
index 0000000..d915ddd
--- /dev/null
+++ b/tools/build/libgen/src/main/java/org/onosproject/libgen/BuckLibGenerator.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * 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.
+ */
+package org.onosproject.libgen;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Generates a BUCK file from a JSON file containing third-party library
+ * dependencies.
+ */
+public class BuckLibGenerator {
+
+//    public static final String MAVEN_COORDS = "maven_coords";
+//    public static final String COMPILE_ONLY = "compile_only";
+//    public static final String RUNTIME_ONLY = "runtime_only";
+
+    private final ObjectNode jsonRoot;
+    private final List<BuckArtifact> artifacts = new ArrayList<>();
+    private final List<BuckLibrary> libraries = new ArrayList<>();
+
+    /**
+     * Main entry point.
+     *
+     * @param args command-line arguments; JSON input file and BUCK output file
+     */
+    public static void main(String[] args) throws Exception {
+        if (args.length < 2) {
+            System.err.println("Not enough args.\n\nUSAGE: <json file> <output>");
+            System.exit(5);
+        }
+
+        // Parse args
+        String jsonFilePath = args[0];
+        String outputBuckPath = args[1];
+
+        // Load and parse input JSON file
+        ObjectMapper mapper = new ObjectMapper();
+        mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
+        ObjectNode json = (ObjectNode) mapper.reader()
+                .readTree(new FileInputStream(jsonFilePath));
+
+        // Traverse dependencies and build a dependency graph (DAG)
+        BuckLibGenerator generator = new BuckLibGenerator(json).resolve();
+
+        // Write the output BUCK file
+        generator.write(outputBuckPath);
+        System.out.printf("\nFinish writing %s\n", outputBuckPath);
+    }
+
+    public BuckLibGenerator(ObjectNode root) {
+        this.jsonRoot = root;
+    }
+
+    private BuckArtifact parseArtifact(Map.Entry<String, JsonNode> entry) {
+        String name = entry.getKey();
+        JsonNode value = entry.getValue();
+        String uri;
+        String repo = null;
+        if (value.isTextual()) {
+            uri = value.asText();
+        } else if (value.isObject()) {
+            uri = value.get("uri").asText();
+            repo = value.get("repo").asText("");
+        } else {
+            throw new RuntimeException("Unknown element for name: " + name +
+                                       " of type: " + value.getNodeType());
+        }
+
+        System.out.print(name + " ");
+        System.out.flush();
+        BuckArtifact buckArtifact;
+        if (uri.startsWith("http")) {
+            String sha = getHttpSha(uri);
+            buckArtifact = BuckArtifact.getArtifact(name, uri, sha);
+        } else if (uri.startsWith("mvn")) {
+            uri = uri.replaceFirst("mvn:", "");
+//            if (repo != null) {
+//                System.out.println(name + " " + repo);
+//            }
+            buckArtifact = AetherResolver.getArtifact(name, uri, repo);
+        } else {
+            throw new RuntimeException("Unsupported artifact uri: " + uri);
+        }
+        System.out.println(buckArtifact.url());
+        return buckArtifact;
+    }
+
+    private BuckLibrary parseLibrary(Map.Entry<String, JsonNode> entry) {
+        String libraryName = entry.getKey();
+        JsonNode list = entry.getValue();
+        if (list.size() == 0) {
+            throw new RuntimeException("Empty library: " + libraryName);
+        }
+
+        List<String> libraryTargets = new ArrayList<>(list.size());
+        list.forEach(node -> {
+            String name;
+            if (node.isObject()) {
+                name = node.get("name").asText();
+            } else if (node.isTextual()) {
+                name = node.asText();
+            } else {
+                throw new RuntimeException("Unknown node type: " + node.getNodeType());
+            }
+            if (!name.contains(":")) {
+                name = ':' + name;
+            }
+            libraryTargets.add(name);
+        });
+
+        return BuckLibrary.getLibrary(libraryName, libraryTargets);
+    }
+
+    public BuckLibGenerator resolve() {
+        jsonRoot.get("artifacts").fields().forEachRemaining(entry -> {
+            BuckArtifact buckArtifact = parseArtifact(entry);
+            artifacts.add(buckArtifact);
+//            String artifactName = buckArtifact.name();
+//            if (artifacts.putIfAbsent(artifactName, buckArtifact) != null) {
+//                error("Duplicate artifact: %s", artifactName);
+//            }
+        });
+
+        jsonRoot.get("libraries").fields().forEachRemaining(entry -> {
+            BuckLibrary library = parseLibrary(entry);
+            libraries.add(library);
+//            String libraryName = library.name();
+//            if (libraries.putIfAbsent(libraryName, library) != null) {
+//                error("Duplicate library: %s", libraryName);
+//            }
+        });
+
+        return this;
+    }
+
+    void write(String outputFilePath) {
+        File outputFile = new File(outputFilePath);
+        if (!outputFile.setWritable(true)) {
+            error("Failed to make %s to writeable.", outputFilePath);
+        }
+        try (PrintWriter writer = new PrintWriter(outputFile)) {
+            writer.write(String.format(
+                    "# ***** This file was auto-generated at %s. Do not edit this file manually. *****\n",
+                    new Date().toString()));
+            libraries.forEach(library -> writer.print(library.getBuckFragment()));
+            artifacts.forEach(artifact -> writer.print(artifact.getBuckFragment()));
+            writer.flush();
+        } catch (FileNotFoundException e) {
+            error("File not found: %s", outputFilePath);
+        }
+        if (!outputFile.setReadOnly()) {
+            error("Failed to set %s to read-only.", outputFilePath);
+        }
+    }
+
+    String getHttpSha(String url) {
+        //TODO look in buck-out/gen first
+        //FIXME need http download cache
+        try {
+            URLConnection connection = new URL(url).openConnection();
+            connection.connect();
+            InputStream stream = connection.getInputStream();
+            MessageDigest md = MessageDigest.getInstance("SHA-1");
+
+            byte[] buffer = new byte[8192];
+            int read;
+            while ((read = stream.read(buffer)) >= 0) {
+                md.update(buffer, 0, read);
+            }
+            StringBuilder result = new StringBuilder();
+            byte[] digest = md.digest();
+            for (byte b : digest) {
+                result.append(String.format("%02x", b));
+            }
+            return result.toString();
+        } catch (IOException | NoSuchAlgorithmException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void error(String format, String... args) {
+        if (!format.endsWith("\n")) {
+            format += '\n';
+        }
+        System.err.printf(format, args);
+        System.exit(1);
+    }
+}
diff --git a/tools/build/libgen/src/main/java/org/onosproject/libgen/BuckLibrary.java b/tools/build/libgen/src/main/java/org/onosproject/libgen/BuckLibrary.java
new file mode 100644
index 0000000..1eaa978
--- /dev/null
+++ b/tools/build/libgen/src/main/java/org/onosproject/libgen/BuckLibrary.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * 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.
+ */
+package org.onosproject.libgen;
+
+import org.eclipse.aether.artifact.Artifact;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Representation of a java library for Buck.
+ */
+public class BuckLibrary {
+
+    private final String name;
+    private final List<String> targets;
+
+    private final Set<Artifact> provided = new HashSet<>();
+    private final Set<Artifact> runtime = new HashSet<>();
+
+    public static BuckLibrary getLibrary(String libraryName, List<String> libraryTargets) {
+        return new BuckLibrary(libraryName, libraryTargets);
+    }
+
+    private BuckLibrary(String name, List<String> targets) {
+        this.name = name;
+        this.targets = targets;
+    }
+
+    public String name() {
+        return name;
+    }
+
+    public String getBuckFragment() {
+        StringBuilder output = new StringBuilder()
+                .append("osgi_feature_group(\n")
+                .append(String.format("  name = '%s',\n", name))
+                .append("  visibility = ['PUBLIC'],\n")
+                .append("  exported_deps = [");
+
+        targets.forEach(target -> output.append(String.format("\n    '%s',", target)));
+        output.append("\n  ],\n)\n\n");
+
+        return output.toString();
+    }
+}
\ No newline at end of file
diff --git a/tools/build/onos-lib-gen b/tools/build/onos-lib-gen
new file mode 100755
index 0000000..1bed9cf
--- /dev/null
+++ b/tools/build/onos-lib-gen
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+cd $(buck root 2>/dev/null)
+
+java -jar $(buck build //tools/build/libgen:libgen --show-output 2>/dev/null | tail -1 | cut -d' ' -f2) lib/deps.json lib/BUCK
diff --git a/tools/package/BUCK b/tools/package/BUCK
index 31896f3..c02b4c9 100644
--- a/tools/package/BUCK
+++ b/tools/package/BUCK
@@ -100,10 +100,11 @@
 sources = [ '$(location :onos-features)', ]
 sources += staged_repos + staged_apps
 
+import time
 genrule(
   name = 'onos-package',
   out = 'onos.tar.gz',
-  bash = '$(exe //buck-tools:onos-stage) $OUT $(location :onos-karaf) ' + ' '.join(sources),
+  bash = 'echo %s >/dev/null; $(exe //buck-tools:onos-stage) $OUT $(location :onos-karaf) ' % time.time() + ' '.join(sources),
   visibility = [ 'PUBLIC' ],
 )