Tiga platform terbesar tinggal menyisakan satu: macOS — ekosistem dengan pengguna yang paling terbiasa mengunduh aplikasi dari luar App Store, sekaligus yang pintu masuknya paling ketat. Berbeda dengan Linux yang dibuka lewat manajer paket atau Windows yang bisa memaklumi installer unsigned, macOS menegakkan satu aturan baku: aplikasi yang datang dari internet harus terstruktur sebagai bundle .app, bertanda tangan, dan dinotarisasi Apple sebelum Gatekeeper membiarkannya berjalan tanpa peringatan. Kabar baiknya, seluruh konsep section ini tetap berlaku — runtime dependencies, bundle mandiri, cross-compilation, code signing — hanya nama pemainnya yang berganti. Karena praktikum ini berjalan di Linux, semua perintah Apple kita tampilkan sebagai pola yang merujuk dokumentasi resmi tanpa klaim eksekusi, sementara program contohnya tetap diuji sungguhan di sini.

Program Portabel sebagai Bahan Uji #

Kebutuhan source untuk macOS sama persis dengan chapter-chapter sebelumnya: standard library saja, tanpa API spesifik OS. Program konversi berat berikut memenuhi syarat itu:

cpp
#include <iomanip>
#include <iostream>

// Program portabel untuk latihan deployment macOS: tabel konversi
// berat kg ke pound. Hanya standard library, tanpa API spesifik OS.
int main(int argc, char* argv[]) {
    if (argc < 2) {
        std::cerr << "Pemakaian: konverter-berat <kg>\n";
        return 1;
    }

    double kg = std::stod(argv[1]);
    constexpr double KG_PER_POUND = 0.45359237;

    std::cout << std::fixed << std::setprecision(2);
    std::cout << "kg     -> pound\n";
    for (int i = 0; i < 3; ++i) {
        double nilai = kg + i * 5.0;
        std::cout << nilai << " -> " << nilai / KG_PER_POUND << '\n';
    }
    return 0;
}

Output yang Dihasilkan:

text
kg     -> pound
5.00 -> 11.02
10.00 -> 22.05
15.00 -> 33.07

Baris 6-10 menagih satu argumen dan kembali dengan exit code 1 plus pesan pemakaian ke std::cerr bila kosong — disiplin yang sama dengan program rilis di Chapter Release Build. Baris 12-13 mengubah teks argumen menjadi double lewat std::stod dan menetapkan konstanta konversi sebagai constexpr. Baris 15-19 memformat uang ke presisi dua desimal dengan std::fixed, lalu mencetak tabel tiga baris dengan langkah 5 kg. Tidak ada satu pun header di luar iomanip dan iostream, sehingga source yang sama dikompilasi bersih oleh g++ di praktikum maupun clang++ di macOS.

Bundle .app: Struktur yang Disembunyikan Finder #

Di macOS, aplikasi bukan satu berkas executable melainkan direktori bernama *.app dengan tata letak yang disepakati sistem. Finder menyembunyikan struktur ini dan menampilkannya sebagai satu ikon — klik kanan lalu Show Package Contents membuka isi aslinya:

text
KonverterBerat.app/
└── Contents/
    ├── Info.plist
    ├── MacOS/
    │   └── konverter-berat
    └── Resources/

Direktori Contents/MacOS/ menampung executable (dan library milik aplikasi, mengikuti pola bundle $ORIGIN dari Chapter Dynamic Library Deployment), Contents/Resources/ berisi ikon, file lokal terjemahan, dan aset lainnya, sedangkan Info.plist adalah kartu identitas bundle dalam format XML:

text Contoh Info.plist minimal
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>CFBundleExecutable</key>      <string>konverter-berat</string>
  <key>CFBundleIdentifier</key>      <string>com.example.konverterberat</string>
  <key>CFBundleName</key>            <string>KonverterBerat</string>
  <key>CFBundleShortVersionString</key> <string>1.0.0</string>
  <key>CFBundleVersion</key>         <string>1</string>
  <key>LSMinimumSystemVersion</key>  <string>11.0</string>
</dict>
</plist>

Empat kunci terpenting: CFBundleExecutable harus persis nama biner di MacOS/; CFBundleIdentifier adalah identitas reverse-DNS (com.example.konverterberat) yang menjadi nama resmi aplikasi di mata codesign dan sistem; CFBundleShortVersionString memuat versi yang dilihat pengguna (1.0.0 — penomorannya akan kita disiplinkan di Chapter Versioning dan Release); dan CFBundleVersion adalah nomor build internal yang boleh berbeda dari versi publik. Membangunnya manual pun sederhana — kompilasi langsung ke dalam struktur tersebut:

bash Contoh — di mesin macOS (clang++ bawaan)
mkdir -p KonverterBerat.app/Contents/MacOS KonverterBerat.app/Contents/Resources
clang++ -std=c++23 -O2 konverter-berat.cpp \
        -o KonverterBerat.app/Contents/MacOS/konverter-berat

Toolchain bawaan macOS adalah clang++, bukan g++, tetapi flag yang kita pakai sepanjang course (-std=, -O2, -Wall) identik keduanya. Untuk proyek CMake, generator CPack Bundle merakit struktur .app plus Info.plist secara otomatis dari variabel proyek (lihat Referensi) — pola kerja yang sama dengan generator yang kita sebut di chapter-chapter build system.

Gatekeeper: Penjaga Pertama di Mesin Pengguna #

Aplikasi yang diunduh lewat browser tidak datang sendirian: macOS melampirkan atribut diperluas com.apple.quarantine ke berkasnya, dan saat pengguna membuka aplikasi beratribut itu, Gatekeeper menjalankan pemeriksaan — apakah bundle bertanda tangan sah, dan apakah Apple pernah memeriksanya (notarized). Aplikasi tanpa tanda tangan akan ditolak dengan dialog “cannot be opened because Apple cannot check it for malicious software”, dan pintu pemakluman lama (klik kanan lalu Open) makin sempit di versi macOS modern. Untuk menguji build sendiri di mesin latihan, atribut itu bisa diperiksa dan dihapus:

bash Contoh — memeriksa dan melepas atribut quarantine (mesin pengembang)
xattr -l ~/Downloads/KonverterBerat.app
xattr -d com.apple.quarantine ~/Downloads/KonverterBerat.app

Trik ini sah untuk mesin pengembang, tetapi bukan jawaban distribusi: kita tidak bisa meminta ribuan pengguna melepas atribut satu per satu. Jawaban resminya adalah memenuhi Gatekeeper dengan cara yang ia akui — tanda tangan Developer ID plus notarization.

codesign dan Notarization: Alur Rilis Ringkas #

Ada dua lapisan yang sering disangka satu. Code signing menandatangani bundle dengan sertifikat Developer ID Application yang diterbitkan Apple Developer Program — sistem kemudian bisa membuktikan bahwa aplikasi benar-benar dari Anda dan belum berubah satu byte pun sejak ditandatangani. Notarization adalah pengiriman bundle itu ke layanan Apple untuk dipindai malware dan diverifikasi tandanya; setelah lolos, Apple menerbitkan tiket yang ditempel ke bundle (staple), sehingga Gatekeeper di mesin pengguna tanpa internet pun bisa menilainya. Alur ringkasnya:

bash Contoh — di mesin macOS
codesign --sign "Developer ID Application: PT Contoh (AB12CD34EF)" \
         --timestamp --options runtime KonverterBerat.app
codesign --verify --strict --verbose=2 KonverterBerat.app
spctl -a -vv KonverterBerat.app

zip -r KonverterBerat.app.zip KonverterBerat.app
xcrun notarytool submit KonverterBerat.app.zip --keychain-profile "profil-akun" --wait
xcrun stapler staple KonverterBerat.app

Membaca urutannya: codesign --sign menandatangani seluruh bundle dengan --options runtime yang mengaktifkan hardened runtime — syarat wajib notarization; codesign --verify memeriksa integritas tanda tangan; dan spctl -a adalah simulasi penilaian Gatekeeper yang pada bundle sah menjawab accepted. Setelah itu bundle diarsipkan ke ZIP, dikirim ke notary service dengan profil keychain berisi kredensial akun, dan tiketnya ditempel dengan stapler. Semangatnya persis signtool di Chapter Deployment di Windows: bukti asal-usul, bukan enkripsi.

Universal Binary: Satu Biner untuk Dua Arsitektur #

Mac modern beredar dalam dua arsitektur: Apple Silicon (arm64) dan Intel (x86_64). Chapter Cross-compilation sudah membuktikan bahwa satu toolchain bisa membangun untuk arsitektur lain; Apple menambahkan satu langkah lebih jauh dengan menyatukan keduanya dalam satu berkas universal binary (dulu disebut fat binary):

bash Contoh — di mesin macOS
clang++ -std=c++23 -O2 -arch arm64 -arch x86_64 konverter-berat.cpp -o konverter-berat
lipo -info konverter-berat
text
Architectures in the fat file: konverter-berat are: x86_64 arm64

Flag -arch dapat diulang sehingga satu kali kompilasi menghasilkan kedua arsitektur, dan lipo -info melaporkan isinya — berkas “gemuk” itu memuat dua salinan kode mesin yang dipilih macOS saat dijalankan. Dua varian lain yang berguna: lipo -create build-arm64 build-x86_64 -output konverter-berat menggabungkan dua build terpisah menjadi satu, dan lipo -thin arm64 mengekstrak satu arsitektur saat debugging. Universal binary menghilangkan transisi Rosetta 2 (lapisan terjemahan x86_64 ke arm64 yang memperlambat aplikasi Intel di Mac Apple), dengan harga ukuran berkas yang menyatu. Dalam CMake, daftar arsitekturnya diatur lewat CMAKE_OSX_ARCHITECTURES (lihat Referensi), jadi satu konfigurasi proyek sudah meliputi keduanya.

Distribusi via Homebrew #

Cara paling idiomatis membagikan aplikasi CLI ke pengguna macOS adalah Homebrew — manajer paket yang berperan seperti apt di Debian: satu perintah brew install mengunduh, memverifikasi, dan memasang aplikasi beserta dependensinya. Satuan resepnya disebut formula, dan kumpulan formula milik Anda tinggal di tap — repository git yang bisa diterbitkan sendiri. Formula minimal untuk program kita:

text Contoh formula Homebrew (Ruby DSL)
class KonverterBerat < Formula
  desc "Tabel konversi berat kg ke pound"
  homepage "https://example.com/konverter-berat"
  url "https://example.com/dist/konverter-berat-1.0.0.tar.gz"
  sha256 "74b3cbb629eef3ce65dec9ee0adfb3b2d1cea5ab53978b12e1e9526d77d672cf"

  def install
    bin.install "konverter-berat"
  end

  test do
    assert_match "5.00", shell_output("#{bin}/konverter-berat 5")
  end
end

Formula memuat dua pernyataan kunci: url menunjuk tarball rilis — persis pola tarball reproducible yang kita rakit di Chapter Deployment di Linux — dan sha256 menaikkan integritas ke Homebrew: checksum tarball yang tidak cocok membuat instalasi ditolak, mekanisme yang akan kita otomatiskan penuh di chapter berikutnya. Blok install menempatkan biner ke jalur milik Homebrew, dan blok test dijalankan brew test untuk memastikan hasil pasangannya benar-benar bekerja. Pengguna akhirnya hanya menulis:

bash Contoh — di mesin pengguna macOS
brew tap contoh/konverter https://example.com/homebrew-konverter.git
brew install konverter-berat
brew upgrade konverter-berat

brew tap mendaftarkan repository formula Anda, brew install memasang dari sana, dan pembaruan selanjutnya mengalir lewat brew upgrade begitu formula Anda menunjuk URL dan checksum versi baru — pembaruan aplikasi menjadi tanggungan manajer paket, bukan fitur yang harus Anda bangun sendiri.

Perhatikan benang merah yang mulai terlihat: identitas yang sama muncul berulang di seluruh mekanisme macOS ini — CFBundleShortVersionString di Info.plist, nama tarball di formula Homebrew, pesan tag rilis, hingga keluaran flag --version program. Sebelum semua identitas itu disatukan menjadi paket yang utuh, kita perlu disiplin menomorinya dan mencatat perubahannya — topik Chapter Versioning dan Release berikutnya.

Referensi: CMAKE_OSX_ARCHITECTURES — CMake Docs , CPack Bundle Generator — CMake Docs .