Installing ubuntu/debian with PXE using a Windows machine

There is an official tutorial on Ubuntu’s website to install Ubuntu over the network via PXE, using a Windows machine as a DHCP/TFTP server.

I managed to make it work after a few hours of struggle because one crucial piece of information was unclear/missing. The same procedure is applicable for Debian.


You need to:

  • delete pxelinux.0 and pxelinux.cfg from the root of the created netboot/ folder
  • copy netboot/ubuntu-installer/amd64/pxelinux.0 to netboot/ (path will vary depending on what you are installing)
  • create a pxelinux.cfg folder in the netboot/ folder
  • copy the proper version of default to the netboot/pxelinux.cfg/ folder you just created (once again, the location will vary depending on what distribution you are installing)

The details above are not required, provided you are an admin when extracting the netboot.tar.gz archive (otherwise the symlinks get lost in translation and you need to manually recreate them).

I used the following options in tftp32:

  • Current directory: C:\tftp\netboot
  • Settings
    • GLOBAL: TFTP Server + DHCP Server
    • TFTP: Base Directory: C:\tftp\netboot | PXE Compatibility | Bind to this adress
    • DHCP: Pool starting address: beginning of an unused IP range | Size: 10 | Boot file: pxelinux.0 | WINS/DNS: DNS IP | Router: Router IP | Mask: network mask | Select 3 options: Ping, Bind, Persistent

And don’t forget to turn off other DHCP servers on your network!

Tagged , ,

A simple logback appender for mongoDB

logback is a logging framework that, among other things, allows to log data to a database. However mongoDB is not one of the supported ones.

But logback is fully configurable so I’ve written a simple appender to use logback with mongo.

/*
 * Copyright 2013 Yann Le Tallec.
 *
 * 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 com.assylias.logging;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.ThrowableProxyUtil;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.MongoClient;
import com.mongodb.MongoException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.slf4j.Marker;

/**
 * A logback appender that uses Mongo to log messages.
 * <p>
 * Typical configuration:
 * <pre> {@code
 * <appender name=\"TEST\" class=\"com.assylias.logging.MongoAppender\">
 *     <host>192.168.1.1</host>
 *     <port>27017</port>
 *     <db>log</db>
 *     <collection>test</collection>
 * </appender>
 * } </pre>
 * The log messages have the following JSON format (the {@code marker}, {@code exception} and {@code stacktrace} fields are optional):
 * <pre> {@code
 * { "_id" : ObjectId("514b2d529234d98131221578"),
 *   "logger" : "com.assylias.logging.MongoAppenderTest",
 *   "timestamp" : ISODate("2013-03-21T15:54:58.357Z"),
 *   "level" : "ERROR",
 *   "marker" : "Marker",
 *   "thread" : "TestNG",
 *   "message" : "An error occurend in the test",
 *   "exception" : "java.lang.RuntimeException: java.lang.Exception",
 *   "stacktrace" : [ "at com.assylias.logging.MongoAppenderTest.testCausedBy(MongoAppenderTest.java:129) ~[test-classes/:na]",
 *                    "at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.7.0_17]",
 *                    "at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) ~[na:1.7.0_17]",
 *                    "Caused by: java.lang.Exception: null",
 *                    "at com.assylias.logging.MongoAppenderTest.testCausedBy(MongoAppenderTest.java:126) ~[test-classes/:na]",
 *                    "... 20 common frames omitted" ],
 * } } </pre>
 * If an error occurs while logging, the message might also contain a {@code logging_error} field:
 * <pre> {@code
 *    "logging_error" : "Could not log all the event information: com.mongodb.MongoInterruptedException: A driver operation has been interrupted
 *                       at com.mongodb.DBPortPool.get(DBPortPool.java:216)
 *                       at com.mongodb.DBTCPConnector$MyPort.get(DBTCPConnector.java:440)
 *                       ..."
 * } </pre>
 */
public class MongoAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {

    private String host;
    private int port;
    private String db;
    private String collection;
    private DBCollection logCollection;

    public MongoAppender() {
    }

    @Override
    public void start() {
        try {
            connect();
            super.start();
        } catch (UnknownHostException | MongoException e) {
            addError("Can't connect to mongo: host=" + host + ", port=" + port, e);
        }
    }

    private void connect() throws UnknownHostException {
        MongoClient client = new MongoClient(host, port);
        DB mongoDb = client.getDB(db == null ? "log" : db);
        logCollection = mongoDb.getCollection(collection == null ? "log" : collection);
    }

    @Override
    protected void append(ILoggingEvent evt) {
        if (evt == null) return; //just in case

        DBObject log = getBasicLog(evt);
        try {
            logException(evt.getThrowableProxy(), log);
            logCollection.insert(log);
        } catch (Exception e) {
            try {
                StringWriter sw = new StringWriter();
                e.printStackTrace(new PrintWriter(sw));
                log.put("logging_error", "Could not log all the event information: " + sw.toString());
                log.put("level", "ERROR");
                logCollection.insert(log);
            } catch (Exception e2) { //really not working
                addError("Could not insert log to mongo: " + evt, e2);
            }
        }
    }

    private DBObject getBasicLog(ILoggingEvent evt) {
        DBObject log = new BasicDBObject();
        log.put("logger", evt.getLoggerName());
        log.put("timestamp", new Date(evt.getTimeStamp()));
        log.put("level", String.valueOf(evt.getLevel())); //in case getLevel returns null
        Marker m = evt.getMarker();
        if (m != null) {
            log.put("marker", m.getName());
        }
        log.put("thread", evt.getThreadName());
        log.put("message", evt.getFormattedMessage());
        return log;
    }

    private void logException(IThrowableProxy tp, DBObject log) {
        if (tp == null) return;
        String tpAsString = ThrowableProxyUtil.asString(tp); //the stack trace basically
        List<String> stackTrace = Arrays.asList(tpAsString.replace("\t","").split(CoreConstants.LINE_SEPARATOR));
        if (stackTrace.size() > 0) {
            log.put("exception", stackTrace.get(0));
        }
        if (stackTrace.size() > 1) {
            log.put("stacktrace", stackTrace.subList(1, stackTrace.size()));
        }
    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public String getDb() {
        return db;
    }

    public void setDb(String db) {
        this.db = db;
    }

    public String getCollection() {
        return collection;
    }

    public void setCollection(String collection) {
        this.collection = collection;
    }
}

Here is an excerpt from the class javadoc.

Typical configuration:

<appender name=\"TEST\" class=\"com.assylias.logging.MongoAppender\">
    <host>192.168.1.1</host>
    <port>27017</port>
    <db>log</db>
    <collection>test</collection>
</appender>

The log messages have the following JSON format (the marker, exception and stacktrace fields are optional):

{ "_id" : ObjectId("514b2d529234d98131221578"),
  "logger" : "com.assylias.logging.MongoAppenderTest",
  "timestamp" : ISODate("2013-03-21T15:54:58.357Z"),
  "level" : "ERROR",
  "marker": "A_MARKER",
  "thread" : "TestNG",
  "message" : "An error occurend in the test",
  "exception" : "java.lang.RuntimeException: java.lang.Exception",
  "stacktrace" : [ "at com.assylias.logging.MongoAppenderTest.testCausedBy(MongoAppenderTest.java:129) ~[test-classes/:na]",
                   "at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.7.0_17]",
                   "at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) ~[na:1.7.0_17]",
                   "Caused by: java.lang.Exception: null",
                   "at com.assylias.logging.MongoAppenderTest.testCausedBy(MongoAppenderTest.java:126) ~[test-classes/:na]",
                   "... 20 common frames omitted" ],
} 

If an error occurs while logging, the message might also contain a logging_error field:

  "logging_error" : "Could not log all the event information: com.mongodb.MongoInterruptedException: A driver operation has been interrupted
                  at com.mongodb.DBPortPool.get(DBPortPool.java:216)
                  at com.mongodb.DBTCPConnector$MyPort.get(DBTCPConnector.java:440)
                  ..."
Tagged , , ,

Posting in WordPress.com using Markdown

This post is obsolete since WordPress now allows to write posts with Markdown directly (in the dashboard: Settings / Writing / Check the Markdown box).
I’m leaving it for the record and MarkdownPad is still a nice tool.

I’m getting a little tired of composing my posts in HTML so I’ve downloaded MarkdownPad which enables to convert from Markdown to HTML.

I am fairly used to StackExchange websites which use Markdown so typing a post will become much quicker and easier 😉

Once the text is finalised in MarkdownPad, Tools > Preview Markdown in Browser (F6) then right click > view source in your favourite browser, copy paste the body to WordPress.com et voila.

The only caveat is that code is formatted as html code but looks better when using the [sourcecode] tag which adds nice-looking syntax highlighting. This has to be done manually.

Tagged ,

Java Memory Model and reordering

This is not another post about how to solve the double checked locking idiom. The aim here is to understand what could go wrong without synchronization.

One of the most important promises of the Java Memory Model (JMM) is:

If a program is correctly synchronized, then all executions of the program will appear to be sequentially consistent.
This is an extremely strong guarantee for programmers. Programmers do not need to reason about reorderings to determine that their code contains data races. Therefore they do not need to reason about reorderings when determining whether their code is correctly synchronized. Once the determination that the code is correctly synchronized is made, the programmer does not need to worry that reorderings will affect his or her code.

The following code, taken from Java Concurrency in Practice, is not thread safe because accesses to the shared resource variable are not properly synchronized:

public class UnsafeLazyInitialization {
    private static Resource resource;

    public static Resource getInstance() {
        if (resource == null) //1
            resource = new Resource();  //2
        return resource; //3
    }
}

I will detail below some of the problems that could happen when running that innocent piece of code in a multi-threaded environment. In particular:

  • resource could be instantiated more than once
  • getInstance could return an object in an inconsistent state
  • more interestingly, getInstance could return null

Multiple instantiation

This is the most obvious issue – the code uses a check-then-act pattern so it is very possible that two threads could arrive at the same time in the method, both see resource as null and both initialise the variable. We how have 2 instances of what was supposed to be a singleton.

Improperly constructed object

This one is less obvious – line 2 looks like it is atomic but it isn’t as the JVM needs to (among other things):

  1. allocate some memory
  2. create the new object
  3. initialise its fields with their default value (false for boolean, 0 for other primitives, null for objects)
  4. run the constructor, which includes running parent constructors too
  5. assign the reference to the newly constructed object to resource

Because there is no synchronization, the JMM allows a JVM to perform these steps in virtually any order. See for example this famous discussion about double checked locking that shows that some JIT compilers do run step 5 before step 4.
So getInstance could return a reference to a non-null but inconsistent object (with un-initialised fields).

getInstance can return null

This is even less obvious. It is difficult to imagine an execution path that could return null with such a simple code. However the JMM allows it. To understand why this is possible, we need to analyse the reads and writes in details and assess whether there is a happens-before relationship between them. The code can be rewritten as follows to clearly show the reads and writes:

                               Thread 0
---------------------------------------------------------------------
 10: resource = null; //default value                                  //write
=====================================================================
           Thread 1               |          Thread 2                
----------------------------------+----------------------------------
 11: a = resource;                | 21: x = resource;                  //read
 12: if (a == null)               | 22: if (x == null)               
 13:   resource = new Resource(); | 23:   resource = new Resource();   //write
 14: b = resource;                | 24: y = resource;                  //read
 15: return b;                    | 25: return y;                    

The JLS #17.4.5 gives the rules for a read to be allowed to observe a write:

We say that a read r of a variable v is allowed to observe a write w to v if, in the happens-before partial order of the execution trace:

  • r is not ordered before w (i.e., it is not the case that hb(r, w)), and
  • there is no intervening write w’ to v (i.e. no write w’ to v such that hb(w, w’) and hb(w’, r)).

In this example, both 21 and 24 are therefore allowed to observe 10 or 13 and a legal execution of the program is (assuming thread 1 sees resource null and initialises it):

  1. 21: x = not null (reads the write line 13)
  2. 22: false
  3. 24: y = null (reads the write line 10)
  4. 25: return null

Instructions reordering

In practice, T2 is not going to see a null value after having seen a non-null value, but either the compiler or the JVM or the JIT can reorder instructions in a way that will produce a similar execution. For eaxmple, a possible reordering (with a theoretical execution) would be:

public class UnsafeLazyInitialization {
    private static Resource resource;

    public static Resource getInstance() {
        Resource temp = resource; //null in T1 and T2
        if (resource == null) //null in T1 but not in T2 because it has been initialised by T1 in the meantime
            resource = temp = new Resource(); //only executed by T1
        return temp; //T1 returns the new value, T2 returns null
    }
}

This reordering, although it makes little sense, is perfectly valid because it does not affect the intra-thread semantics (if run in a single-threaded environment, it will produce the same result as the original code).

Conclusion

This example shows that even on a fairly contrived example the outcome of an improperly synchronized program can be quite surprising. Although it is unlikely that any compilers would actually perform that reordering, a more complex situation could quickly become impossible to analyse.
Bottom line: saving on synchronization is not an option.

Tagged , ,

Size of objects in Java

I need to optimise the size taken by some of my objects as there will be millions of them and a factor of 3 starts to make a difference on a standard desktop PC, especially if it is a 32 bit machine.

JVM parameters

To get (fairly) accurate results using Runtime.getRuntime().freeMemory() I have used the following JVM parameters (on hotspot 7u11 x64 – some parameters might not be available on other JVMs): -server -Xms2000m -Xmx2000m -verbose:gc -XX:-UseTLAB -XX:+UseCompressedOops. The various flags do the following:

  • -Xms and -Xmx are well known and respectiely define the initial and maximum heap size. I set them high (2GB) to prevent the garbage collector from running during the test.
  • -verbose:gc makes the JVM print to the console when a GC is run. That enables to visually control that no GC ran during the tests.
  • -XX:-UseTLAB asks the JVM not to allocate memory in chunks (which it otherwise does for efficiency). Turning that option off gives more accurate and stable results.
  • -XX:+UseCompressedOops asks the JVM to compress references from 8 to 4 bytes. Note that it is on by default on Hotspot 7 x64.

Tests

The objective of my test was to determine the size of various classes that I could use to store tick data. The issue being that some ticks come with extra information and some don’t. I was wondering whether I should declare all the possible fields and leave them null when not available, or have an array or EnumMap instead. The only constraint here being memory footprint.

There are a few interesting observations – they are obviously empirical and might vary depending on several factors, including architecture (32/64 bits), JVM, other?:

  • an int takes 4 bytes
  • BUT an empty class (no members) and a class that has an int take the same space in memory (16 bytes), due to memory alignement
  • a null reference takes 4 bytes (same as int, due to compressed oop), with the same alignement observation
  • whether members are instantiated when declared or via a constructor does not make a difference (4 bytes)
  • an Object takes 16 bytes in memory
  • Adding 6 null Strings to my class (from TickBasic to TickComplete) adds 24 bytes, i.e. a size increase of 50%
  • The natural design is better than trying to use a “lazy” data structure with EnumMap

The test code at the bottom is fairly extensive but not very complicated. The output is (64 bits machine):

Object: 16 bytes
Object array (empty): 4 bytes
Object array (full) - incremental size: 16 bytes
Object with 1 int: 16 bytes
Object with 2 ints: 24 bytes
Object with 3 ints: 24 bytes
Object with 1 long: 24 bytes
Object with 2 longs: 32 bytes
Object with 3 longs: 40 bytes
Object with 1 null Object: 16 bytes
Object with 2 null Objects: 24 bytes
Object with 3 null Objects: 24 bytes
Object with 1 allocated Object: 16 bytes
Object with 2 allocated Objects: 24 bytes
Object with 3 allocated Objects: 24 bytes
TickBasic: 56 bytes
TickComplete: 80 bytes
TickCompleteWithConstructor: 80 bytes
TickArray: 128 bytes
TickEnumMap: 112 bytes

Test Code

public class TestMemory {

    private static final int SIZE = 100_000;
    private static Runnable r;
    private static Object[] array;
    private static Object o;
    private static Object o1 = new Object();
    private static Object o2 = new Object();
    private static Object o3 = new Object();

    private static void test(Runnable r, String name, int numberOfObjects) {
        long mem = Runtime.getRuntime().freeMemory();
        r.run();
        System.out.println(name + ": " + (mem - Runtime.getRuntime().freeMemory()) / numberOfObjects + " bytes");
    }

    public static void main(String[] args) throws Exception {
        System.out.println(System.getProperty("java.vm.name"));
        DateTime date = new DateTime(); //for some reason the result gets biased if we don't initialise DateTime first

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new Object(); } };
        test(r, "Object", SIZE);

        r = new Runnable() { public void run() { array = new Object[SIZE]; } };
        test(r, "Object array (empty)", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) array[i] = new Object(); } };
        test(r, "Object array (full) - incremental size", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith1Int(); } };
        test(r, "Object with 1 int", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith2Ints(); } };
        test(r, "Object with 2 ints", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith3Ints(); } };
        test(r, "Object with 3 ints", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith1Long(); } };
        test(r, "Object with 1 long", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith2Longs(); } };
        test(r, "Object with 2 longs", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith3Longs(); } };
        test(r, "Object with 3 longs", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith1NullObject(); } };
        test(r, "Object with 1 null Object", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith2NullObjects(); } };
        test(r, "Object with 2 null Objects", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith3NullObjects(); } };
        test(r, "Object with 3 null Objects", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith1Object(); } };
        test(r, "Object with 1 allocated Object", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith2Objects(); } };
        test(r, "Object with 2 allocated Objects", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith3Objects(); } };
        test(r, "Object with 3 allocated Objects", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new TickBasic(); } };
        test(r, "TickBasic", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new TickComplete(); } };
        test(r, "TickComplete", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new TickCompleteWithConstructor(new DateTime(), TickType.TRADE); } };
        test(r, "TickCompleteWithConstructor", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new TickArray(); } };
        test(r, "TickArray", SIZE);

        r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new TickEnumMap(); } };
        test(r, "TickEnumMap", SIZE);
    }

    public static class ObjectWith1Int  { int i; }
    public static class ObjectWith2Ints { int i, j; }
    public static class ObjectWith3Ints { int i, j, k; }
    public static class ObjectWith1Long  { long i; }
    public static class ObjectWith2Longs { long i, j; }
    public static class ObjectWith3Longs { long i, j, k; }
    public static class ObjectWith1NullObject  { Object o; }
    public static class ObjectWith2NullObjects { Object o, p; }
    public static class ObjectWith3NullObjects { Object o, p, q; }
    public static class ObjectWith1Object  { Object o = o1; }
    public static class ObjectWith2Objects { Object o = o1; Object p = o2; }
    public static class ObjectWith3Objects { Object o = o1; Object p = o2; Object q = o3; }

    public static class TickBasic {
        DateTime time = new DateTime();
        TickType type = TickType.TRADE;
        double value;
        int size;
    }

    public static class TickComplete {
        DateTime time = new DateTime();
        TickType type = TickType.TRADE;
        double value;
        int size;
        String cc;
        String ec;
        String mc;
        String bbc;
        String bsc;
        String rc;
    }

    public static class TickCompleteWithConstructor {
        DateTime time;
        TickType type;
        double value;
        int size;
        String cc;
        String ec;
        String mc;
        String bbc;
        String bsc;
        String rc;

        public TickCompleteWithConstructor(DateTime time, TickType type) {
            this.time = time;
            this.type = type;
        }
    }

    public static class TickArray {
        Object[] o = new Object[TickFields.values().length];
    }

    public static class TickEnumMap {
        Map values = new EnumMap(TickFields.class);
    }

    public static enum TickFields {

        TIME("time"),
        TYPE("type"),
        VALUE("value"),
        SIZE("size"),
        CONDITION_CODE("conditionCode"),
        EXCHANGE_CODE("exchangeCode"),
        MIC_CODE("micCode"),
        BROKER_BUY_CODE("brokerBuyCode"),
        BROKER_SELL_CODE("brokerSellCode"),
        RPS_CODE("rpsCode");
        private String code;

        TickFields(String code) {
            this.code = code;
        }
    }

    public static enum TickType {

        TRADE,
        BID,
        MID,
        ASK;
    }
}
Tagged , , ,

Bloomberg Java API Wrapper: jBloomberg

jBloomberg is an open source (Apache 2) Java library that wraps the low level Bloomberg Java API. A couple of usage examples are given at the bottom of the package javadoc page .

The main advantages of this library vs. the Bloomberg API are:

  • Less string based configuration: whenever possible I have used enums to remove the typos issues
  • Straight forward: retrieving historical data literally takes 5 lines of code, whereas when using the Bloomberg API, the code gets quickly cluttered with parsing, error handling and so on
  • Builder based design: most queries to Bloomberg are prepared with builders using the fluent interface pattern
  • The library takes thread safety seriously (so does the Bloomberg API) – all actions / objects are thread safe and can be used in a multi threaded application
  • Uses the standard java.util.concurrent package objects, so the syntax / way of doing things should look familiar to Java developers. For example, a historical data request returns a Future<RequestResult>
Tagged ,

Upload javadoc to github

I have uploaded a new project to github yesterday but wanted to find a way to also add a way to link to a browsable javadoc.

I have generated the javadoc from Netbeans (using Maven site:site) and the question was how to upload it to github. I used the following steps:

  1. Create a gh page on github: on the project page > Settings > GitHub Pages > Automatic Page Generator. That creates a new branch to the project which can contain HTML documentation.
  2. Download and install Git SCM (or use any other git client you might like)
  3. Once the email confirming the creation of the page is received, clone the gh-page branch to the local computer – I did it from Netbeans but I think the command is (from the git client):
    $ cd your_repo_root/repo_name
    $ git fetch origin
    $ git checkout gh-pages
  4. Edit and amend the index.html page as required. Mine looks like this
  5. Copy the javadoc folder to the local repository
  6. Add it to your repo, commit and push:
    $ git add apidocs
    $ git commit -a
    $ git push --repo=https://github.com/assylias/jBloomberg.git
  7. Add a link to those beautiful pages in your README.md file, which appears on the project’s main page, and you’re done.

Notes:

  • I had to setup the user.name and user.email global parameters before committing, but it was clearly indicated by an error message.
  • The commit message in Git SCM is done in a VIM like environment => type I to insert the message then ESC to exit the editing mode and : x to exit and save.
Tagged , ,