Library yang bagus bukan hanya kode yang bekerja — juga dokumentasi yang lengkap dan prosedur release yang jelas. Pada bagian ini kita menulis README.md yang menjelaskan cara build, API publik, dan kode error; menetapkan strategi versioning dengan konstanta EXAMPLELIB_VERSION_MAJOR/MINOR/PATCH; serta menyusun target install di Makefile untuk mendistribusikan library ke direktori sistem. Tujuan bagian ini adalah memastikan konsumen lain dapat memakai library tanpa membaca source code.

Konsep #

Dokumentasi library minimal terdiri dari empat bagian: (1) cara build (Makefile target dan flag), (2) daftar API publik dengan signature singkat, (3) daftar kode error dan artinya, (4) contoh pemakaian singkat. Format README.md dipilih karena GitHub, GitLab, dan IDE menampilkan markdown secara otomatis.

Versioning mengikuti Semantic Versioning (semver): MAJOR.MINOR.PATCH (mis. 1.0.0). Aturan: naikkan PATCH bila bug fix yang kompatibel, naikkan MINOR bila fitur baru yang kompatibel, naikkan MAJOR bila breaking change. Untuk library C, breaking change meliputi perubahan signature fungsi, hapus fungsi publik, atau ubah makna enum. Konstanta versi di examplelib.h (EXAMPLELIB_VERSION_MAJOR dst.) memungkinkan konsumen cek versi saat preprocessor atau runtime.

Install target meng-copy library ke direktori sistem. Standar UNIX: header ke PREFIX/include/, library ke PREFIX/lib/. Default prefix /usr/local (tidak butuh sudo untuk menulis di sana). Setelah install, konsumen dapat compile dengan -I/usr/local/include dan link dengan -L/usr/local/lib -lexamplelib tanpa menentukan path source proyek.

Implementasi #

README.md untuk examplelib (ringkas):

markdown
# examplelib

Contoh library C yang reusable untuk pembelajaran cara membangun,
menguji, dan mendistribusikan library C.

## Build

    make             # Bangun static + shared + test
    make static      # Hanya libexamplelib.a
    make shared      # Hanya libexamplelib.so
    make test        # Build dan jalankan test
    make install     # Install ke /usr/local (perlu sudo)

## API Singkat

| Fungsi                  | Tugas                                       |
|-------------------------|----------------------------------------------|
| `exlib_version()`       | Versi library sebagai string                 |
| `exlib_mean()`          | Hitung rata-rata array double                |
| `exlib_stddev()`        | Hitung standar deviasi populasi              |
| `exlib_reverse_bytes()` | Balik urutan byte in-place                   |
| `exlib_format_size()`   | Format ukuran byte ke "1.50 KB" dst.         |
| `exlib_contains()`      | Cek substring ada di string                  |
| `exlib_strerror()`      | Pesan singkat untuk kode error               |

## Kode Error

| Kode                   | Nilai | Arti                          |
|------------------------|-------|-------------------------------|
| `EXLIB_OK`             | 0     | sukses                        |
| `EXLIB_ERR_NULL`       | -1    | pointer NULL atau n=0         |
| `EXLIB_ERR_RANGE`      | -2    | nilai di luar rentang valid   |
| `EXLIB_ERR_FORMAT`     | -3    | format input tidak valid      |
| `EXLIB_ERR_OVERFLOW`   | -4    | potensi overflow              |
| `EXLIB_ERR_NOT_FOUND`  | -5    | tidak ditemukan               |

Konstanta versi di examplelib.h:

c
#define EXAMPLELIB_VERSION_MAJOR 1
#define EXAMPLELIB_VERSION_MINOR 0
#define EXAMPLELIB_VERSION_PATCH 0
#define EXAMPLELIB_VERSION_STRING "1.0.0"

Target install di Makefile:

makefile
PREFIX ?= /usr/local

install: static shared
        install -d $(PREFIX)/lib
        install -d $(PREFIX)/include
        install -m 644 $(LIB_STATIC) $(PREFIX)/lib/
        install -m 755 $(LIB_SHARED) $(PREFIX)/lib/
        install -m 644 include/examplelib.h $(PREFIX)/include/

Demo program konsumen yang memakai library yang telah di-install:

c
#include <stdio.h>
#include <examplelib.h>

int main(void) {
    printf("Installed lib version: %s\n", exlib_version());
    printf("Major: %d, Minor: %d, Patch: %d\n",
           EXAMPLELIB_VERSION_MAJOR,
           EXAMPLELIB_VERSION_MINOR,
           EXAMPLELIB_VERSION_PATCH);

    double data[] = {10.0, 20.0, 30.0};
    double mean;
    exlib_status s = exlib_mean(data, 3, &mean);
    if (s == EXLIB_OK) {
        printf("Mean [10,20,30] = %.2f\n", mean);
    } else {
        printf("Error: %s\n", exlib_strerror(s));
    }
    return 0;
}

Pengujian #

Install library ke prefix lokal (tidak butuh sudo):

bash
make install PREFIX=/tmp/examplelib_install
ls -la /tmp/examplelib_install/lib /tmp/examplelib_install/include

Output yang Dihasilkan:

text
/tmp/examplelib_install/include/:
total 12
-rw-r--r-- 1 z z 1834 ... examplelib.h

/tmp/examplelib_install/lib/:
total 32
-rw-r--r-- 1 z z 4682 ... libexamplelib.a
-rwxr-xr-x 1 z z 15872 ... libexamplelib.so

Compile program konsumen yang memakai library terinstall (static link):

bash
gcc -std=c11 -Wall -Wextra -I/tmp/examplelib_install/include \
    -o demo_user demo_user.c \
    -L/tmp/examplelib_install/lib -l:libexamplelib.a -lm
./demo_user

Output yang Dihasilkan:

text
Installed lib version: 1.0.0
Major: 1, Minor: 0, Patch: 0
Mean [10,20,30] = 20.00

Verifikasi semua test lulus sebelum release:

bash
make test

Output yang Dihasilkan:

text

=== Hasil Test examplelib ===
Total : 28
Lulus : 28
Gagal : 0

Catatan: macro ASSERT pada test_examplelib.c hanya mencetak baris saat test gagal — saat semua lulus, hanya ringkasan akhir yang muncul. Baris kosong di awal berasal dari \n di printf ringkasan.

Mari kita telaah baris-baris kunci pada README, versioning, dan Makefile install:

  1. Baris # examplelib (README H1): judul proyek. GitHub menampilkannya sebagai judul halaman repo. Konvensi Markdown: H1 (#) di README, H2 (##) untuk section utama.
  2. Baris tabel API Singkat: dokumentasi ringkas. Kolom “Fungsi” memakai backtick untuk kode inline (exlib_version()) — render sebagai monospace di GitHub.
  3. Baris make install # Install ke /usr/local (perlu sudo): contoh pemakaian dengan komentar. Default prefix /usr/local butuh sudo karena scoping sistem; override PREFIX=/tmp/examplelib_install menghindari sudo.
  4. Baris #define EXAMPLELIB_VERSION_MAJOR 1 (header): konstanta integer. Konsumen dapat memeriksa versi saat preprocessor: #if EXAMPLELIB_VERSION_MAJOR >= 1 untuk mengaktifkan fitur yang cuma ada di versi 1+.
  5. Baris #define EXAMPLELIB_VERSION_STRING "1.0.0" (header): string versi. Fungsi exlib_version() memulangkan string ini, sehingga pemanggil dapat menampilkan versi ke pengguna tanpa preprocessor.
  6. Baris PREFIX ?= /usr/local (Makefile): variabel dengan default. Sintaks ?= hanya set bila belum di-set dari environment atau command line. make install PREFIX=/tmp/... meng-override default.
  7. Baris install -d $(PREFIX)/lib (Makefile): buat direktori bila belum ada. install -d membuat direktori dengan permission standar (755).
  8. Baris install -m 644 $(LIB_STATIC) $(PREFIX)/lib/ (Makefile): install static lib. Mode 644 (rw-r–r–) — file dapat dibaca siapa saja tetapi tidak dieksekusi (static lib tidak butuh execute permission).
  9. Baris install -m 755 $(LIB_SHARED) $(PREFIX)/lib/ (Makefile): install shared lib. Mode 755 (rwxr-xr-x) — loader perlu execute permission untuk memetakan .so ke memori.
  10. Baris install -m 644 include/examplelib.h $(PREFIX)/include/ (Makefile): install header publik. Konsumen perlu header ini untuk compile program mereka; tanpa header, fungsi exlib_version tidak dikenal.

Proyek 9 selesai. Library examplelib siap dipakai oleh proyek lain sebagai static atau shared library, lengkap dengan dokumentasi dan install target.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

src/examplelib.c

Unduh berkas
c
/* examplelib.c - Implementasi library examplelib */
#include "examplelib.h"

#include <math.h>
#include <string.h>
#include <stdio.h>

const char *exlib_version(void) {
    return EXAMPLELIB_VERSION_STRING;
}

const char *exlib_strerror(exlib_status code) {
    switch (code) {
        case EXLIB_OK:            return "sukses";
        case EXLIB_ERR_NULL:      return "pointer NULL atau n=0";
        case EXLIB_ERR_RANGE:     return "nilai di luar rentang valid";
        case EXLIB_ERR_FORMAT:    return "format input tidak valid";
        case EXLIB_ERR_OVERFLOW:  return "potensi overflow";
        case EXLIB_ERR_NOT_FOUND: return "tidak ditemukan";
        default:                  return "kode tidak dikenal";
    }
}

exlib_status exlib_mean(const double *data, size_t n, double *out) {
    if (data == NULL || out == NULL || n == 0) {
        return EXLIB_ERR_NULL;
    }
    double sum = 0.0;
    for (size_t i = 0; i < n; i++) {
        sum += data[i];
    }
    *out = sum / (double)n;
    return EXLIB_OK;
}

exlib_status exlib_stddev(const double *data, size_t n, double *out) {
    if (data == NULL || out == NULL || n == 0) {
        return EXLIB_ERR_NULL;
    }
    double mean = 0.0;
    exlib_status s = exlib_mean(data, n, &mean);
    if (s != EXLIB_OK) {
        return s;
    }
    double sum_sq = 0.0;
    for (size_t i = 0; i < n; i++) {
        double diff = data[i] - mean;
        sum_sq += diff * diff;
    }
    *out = sqrt(sum_sq / (double)n);
    return EXLIB_OK;
}

exlib_status exlib_reverse_bytes(uint8_t *data, size_t n) {
    if (data == NULL) {
        return EXLIB_ERR_NULL;
    }
    if (n < 2) {
        return EXLIB_OK;
    }
    size_t i = 0;
    size_t j = n - 1;
    while (i < j) {
        uint8_t tmp = data[i];
        data[i] = data[j];
        data[j] = tmp;
        i++;
        j--;
    }
    return EXLIB_OK;
}

exlib_status exlib_format_size(uint64_t bytes, char *out, size_t out_cap) {
    if (out == NULL) {
        return EXLIB_ERR_NULL;
    }
    if (out_cap < 16) {
        return EXLIB_ERR_RANGE;
    }
    /* Daftar unit dari kecil ke besar. */
    static const char *units[] = {"B", "KB", "MB", "GB", "TB", "PB"};
    static const int n_units = 6;
    if (bytes == 0) {
        snprintf(out, out_cap, "0.00 B");
        return EXLIB_OK;
    }
    int unit_idx = 0;
    double size = (double)bytes;
    while (size >= 1024.0 && unit_idx < n_units - 1) {
        size /= 1024.0;
        unit_idx++;
    }
    snprintf(out, out_cap, "%.2f %s", size, units[unit_idx]);
    return EXLIB_OK;
}

exlib_status exlib_contains(const char *haystack, const char *needle) {
    if (haystack == NULL || needle == NULL) {
        return EXLIB_ERR_NULL;
    }
    if (needle[0] == '\0') {
        return EXLIB_OK;
    }
    if (strstr(haystack, needle) != NULL) {
        return EXLIB_OK;
    }
    return EXLIB_ERR_NOT_FOUND;
}

tests/test_examplelib.c

Unduh berkas
c
/* test_examplelib.c - Unit test untuk examplelib */
#include <stdio.h>
#include <string.h>
#include <math.h>

#include "examplelib.h"

static int tests_run = 0;
static int tests_pass = 0;

#define ASSERT(cond, msg) \
    do { \
        tests_run++; \
        if (cond) { \
            tests_pass++; \
        } else { \
            printf("FAIL: %s (line %d)\n", msg, __LINE__); \
        } \
    } while (0)

static void test_version(void) {
    ASSERT(strcmp(exlib_version(), "1.0.0") == 0,
           "versi harus 1.0.0");
    ASSERT(strcmp(exlib_strerror(EXLIB_OK), "sukses") == 0,
           "strerror OK = 'sukses'");
    ASSERT(strcmp(exlib_strerror(EXLIB_ERR_NOT_FOUND),
                  "tidak ditemukan") == 0,
           "strerror NOT_FOUND sesuai");
}

static void test_mean(void) {
    double data[] = {1.0, 2.0, 3.0, 4.0, 5.0};
    double mean = 0.0;
    exlib_status s = exlib_mean(data, 5, &mean);
    ASSERT(s == EXLIB_OK, "mean harus sukses");
    ASSERT(fabs(mean - 3.0) < 1e-9, "mean = 3.0");

    s = exlib_mean(NULL, 5, &mean);
    ASSERT(s == EXLIB_ERR_NULL, "mean(NULL) = ERR_NULL");

    s = exlib_mean(data, 0, &mean);
    ASSERT(s == EXLIB_ERR_NULL, "mean n=0 = ERR_NULL");
}

static void test_stddev(void) {
    double data[] = {2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0};
    double sd = 0.0;
    exlib_status s = exlib_stddev(data, 8, &sd);
    ASSERT(s == EXLIB_OK, "stddev harus sukses");
    /* Standar deviasi populasi: sqrt(32/8) = 2.0 */
    ASSERT(fabs(sd - 2.0) < 1e-9, "stddev = 2.0");
}

static void test_reverse_bytes(void) {
    uint8_t data[] = {1, 2, 3, 4, 5};
    exlib_status s = exlib_reverse_bytes(data, 5);
    ASSERT(s == EXLIB_OK, "reverse sukses");
    ASSERT(data[0] == 5 && data[1] == 4 && data[2] == 3,
           "data setelah reverse = 5,4,3,2,1");
    ASSERT(data[3] == 2 && data[4] == 1, "data[3..4] = 2,1");

    s = exlib_reverse_bytes(NULL, 5);
    ASSERT(s == EXLIB_ERR_NULL, "reverse NULL = ERR_NULL");

    uint8_t single[] = {42};
    s = exlib_reverse_bytes(single, 1);
    ASSERT(s == EXLIB_OK, "reverse n=1 = OK");
    ASSERT(single[0] == 42, "single byte tidak berubah");
}

static void test_format_size(void) {
    char buf[32];

    exlib_status s = exlib_format_size(0, buf, sizeof(buf));
    ASSERT(s == EXLIB_OK, "format 0 = OK");
    ASSERT(strcmp(buf, "0.00 B") == 0, "0 bytes = '0.00 B'");

    s = exlib_format_size(1024ULL, buf, sizeof(buf));
    ASSERT(s == EXLIB_OK, "format 1024 = OK");
    ASSERT(strcmp(buf, "1.00 KB") == 0, "1024 = '1.00 KB'");

    s = exlib_format_size(1572864ULL, buf, sizeof(buf));
    ASSERT(s == EXLIB_OK, "format 1.5MB = OK");
    ASSERT(strcmp(buf, "1.50 MB") == 0, "1.5MB = '1.50 MB'");

    s = exlib_format_size(1073741824ULL, buf, sizeof(buf));
    ASSERT(s == EXLIB_OK, "format 1GB = OK");
    ASSERT(strcmp(buf, "1.00 GB") == 0, "1GB = '1.00 GB'");

    s = exlib_format_size(100, buf, 5);
    ASSERT(s == EXLIB_ERR_RANGE, "buffer kecil = ERR_RANGE");
}

static void test_contains(void) {
    exlib_status s = exlib_contains("hello world", "world");
    ASSERT(s == EXLIB_OK, "substring ditemukan");

    s = exlib_contains("hello world", "xyz");
    ASSERT(s == EXLIB_ERR_NOT_FOUND, "substring tidak ditemukan");

    s = exlib_contains("abc", "");
    ASSERT(s == EXLIB_OK, "needle kosong = OK");

    s = exlib_contains(NULL, "x");
    ASSERT(s == EXLIB_ERR_NULL, "NULL haystack = ERR_NULL");
}

int main(void) {
    test_version();
    test_mean();
    test_stddev();
    test_reverse_bytes();
    test_format_size();
    test_contains();

    printf("\n=== Hasil Test examplelib ===\n");
    printf("Total : %d\n", tests_run);
    printf("Lulus : %d\n", tests_pass);
    printf("Gagal : %d\n", tests_run - tests_pass);
    return (tests_run == tests_pass) ? 0 : 1;
}