/** Returns the context of this program */
  public synchronized CLProgram build() throws CLBuildException {
    if (built) throw new IllegalThreadStateException("Program was already built !");

    String contentSignature = null;
    File cacheFile = null;
    boolean readBinaries = false;
    if (isCached()) {
      try {
        contentSignature = computeCacheSignature();
        byte[] sha =
            java.security.MessageDigest.getInstance("MD5")
                .digest(contentSignature.getBytes(textEncoding));
        StringBuilder shab = new StringBuilder();
        for (byte b : sha) shab.append(Integer.toHexString(b & 0xff));
        String hash = shab.toString();
        cacheFile = new File(JavaCL.userCacheDir, hash);
        if (cacheFile.exists()) {
          Pair<Map<CLDevice, byte[]>, String> bins =
              readBinaries(
                  Arrays.asList(getDevices()), contentSignature, new FileInputStream(cacheFile));
          setBinaries(bins.getFirst());
          this.source = bins.getSecond();
          assert log(Level.INFO, "Read binaries cache from '" + cacheFile + "'");
          readBinaries = true;
        }
      } catch (Exception ex) {
        assert log(Level.WARNING, "Failed to load cached program", ex);
        entity = null;
      }
    }

    if (entity == null) allocate();

    Runnable deleteTempFiles = null;
    if (!readBinaries)
      try {
        deleteTempFiles = copyIncludesToTemporaryDirectory();
      } catch (IOException ex) {
        throw new CLBuildException(this, ex.toString(), Collections.EMPTY_LIST);
      }

    int nDevices = devices.length;
    cl_device_id[] deviceIds = null;
    if (nDevices != 0) {
      deviceIds = new cl_device_id[nDevices];
      for (int i = 0; i < nDevices; i++) deviceIds[i] = devices[i].getEntity();
    }
    int err =
        CL.clBuildProgram(
            getEntity(), nDevices, deviceIds, readBinaries ? null : getOptionsString(), null, null);
    // int err = CL.clBuildProgram(getEntity(), 0, null, getOptionsString(), null, null);
    if (err != CL_SUCCESS) { // BUILD_PROGRAM_FAILURE) {
      NativeSizeByReference len = new NativeSizeByReference();
      int bufLen = 2048 * 32; // TODO find proper size
      Memory buffer = new Memory(bufLen);

      HashSet<String> errs = new HashSet<String>();
      if (deviceIds == null) {
        error(
            CL.clGetProgramBuildInfo(
                getEntity(), null, CL_PROGRAM_BUILD_LOG, toNS(bufLen), buffer, len));
        String s = buffer.getString(0);
        errs.add(s);
      } else
        for (cl_device_id device : deviceIds) {
          error(
              CL.clGetProgramBuildInfo(
                  getEntity(), device, CL_PROGRAM_BUILD_LOG, toNS(bufLen), buffer, len));
          String s = buffer.getString(0);
          errs.add(s);
        }

      throw new CLBuildException(this, "Compilation failure : " + errorString(err), errs);
    }
    built = true;
    if (deleteTempFiles != null) deleteTempFiles.run();

    if (isCached() && !readBinaries) {
      JavaCL.userCacheDir.mkdirs();
      try {
        Map<CLDevice, byte[]> binaries = getBinaries();
        if (!binaries.isEmpty()) {
          writeBinaries(
              getBinaries(), getSource(), contentSignature, new FileOutputStream(cacheFile));
          assert log(Level.INFO, "Wrote binaries cache to '" + cacheFile + "'");
        }
      } catch (Exception ex) {
        new IOException("[JavaCL] Failed to cache program", ex).printStackTrace();
      }
    }

    return this;
  }
  @Override
  protected synchronized cl_program getEntity() {
    if (entity == null) allocate();

    return entity;
  }