Proyek dan CLI

Struktur berkas fsproj dan pengelolaan solusi dotnet multi proyek lewat CLI.

Daftar isi

Topik ini membahas struktur berkas proyek F# dan pengelolaannya lewat command line .NET, mulai dari anatomi berkas fsproj, pembuatan proyek dengan dotnet new, sampai solusi yang merangkai beberapa proyek sekaligus. Contohnya berupa satu solusi kecil berisi proyek konsol dan proyek pustaka untuk rekap nilai ujian. Setelah topik ini dibaca, pembuatan proyek, penautan antarproyek, serta build dan run dapat dilakukan sepenuhnya dari terminal.

Anatomi berkas fsproj#

Setiap proyek F# diatur oleh satu berkas fsproj berformat XML. Berkas itu mendeklarasikan SDK yang digunakan, properti build, dan daftar berkas kode yang dikompilasi. Proyek baru dibuat dengan perintah dotnet new, dan templat console menghasilkan aplikasi yang dapat dieksekusi langsung dari terminal.

bash
# membuat proyek konsol F# bernama StatistikNilai
dotnet new console -lang F# -o StatistikNilai

Perintah di atas membuat folder StatistikNilai yang berisi dua berkas, yaitu StatistikNilai.fsproj dan Program.fs. Nama proyeknya mengikuti nama folder pada opsi -o. Keluaran perintah ini memuat konfirmasi templat beserta durasi pemulihan paket yang berubah pada setiap eksekusi, sehingga keluarannya tidak ditempelkan pada topik ini. Isi berkas fsproj yang dihasilkan templat adalah sebagai berikut.

xml StatistikNilai.fsproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <Compile Include="Program.fs" />
  </ItemGroup>

</Project>

Atribut Sdk pada elemen Project menunjuk ke Microsoft.NET.Sdk, yaitu kumpulan target build yang menyediakan seluruh langkah kompilasi dan pemulihan paket. Elemen PropertyGroup menampung properti build proyek. Properti OutputType bernilai Exe karena proyek ini dibangun menjadi aplikasi yang dapat dieksekusi, sedangkan TargetFramework bernilai net10.0 yang memilih .NET 10 sebagai platform target. Elemen ItemGroup di bagian bawah mendaftarkan Program.fs sebagai satu-satunya berkas kode yang dikompilasi. Templat mengisi berkas tersebut dengan satu pemanggilan printfn yang mencetak sapaan, sehingga proyek baru dapat langsung dijalankan sebelum satu baris kode pun ditulis.

Proyek pustaka untuk logika domain#

Logika yang akan diuji pada modul ini sebaiknya terpisah dari aplikasi pemanggilnya, dan pemisahan itu dilakukan dengan proyek pustaka kelas (class library). Proyek pustaka dibuat dengan templat classlib yang prosesnya sama persis dengan templat konsol.

bash
# membuat proyek pustaka kelas F# bernama IntiNilai
dotnet new classlib -lang F# -o IntiNilai
xml IntiNilai.fsproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>

  <ItemGroup>
    <Compile Include="Library.fs" />
  </ItemGroup>

</Project>

Berkas fsproj pustaka berbeda dari versi konsol pada dua hal. Properti OutputType tidak ada karena tanpa elemen itu proyek dibangun sebagai library, yaitu rakitan (assembly) yang digunakan proyek lain dan tidak memiliki titik masuk. Properti GenerateDocumentationFile bernilai true sehingga komentar dokumentasi pada kode pustaka dikumpulkan menjadi berkas XML. Berkas Library.fs milik templat diganti dengan modul statistik sederhana berikut.

fsharp Library.fs
// tidak-dijalankan: berkas proyek ini dieksekusi melalui blok bash berikutnya
namespace IntiNilai

// fungsi statistik dasar untuk rekap nilai ujian
module Perhitungan =
    let rataRata (nilai: int list) : float =
        List.averageBy float nilai

    let maksimum (nilai: int list) : int =
        List.max nilai

    let minimum (nilai: int list) : int =
        List.min nilai
bash
# mengisi berkas Library.fs pustaka dari berkas yang ditampilkan di atas
cp Library.fs IntiNilai/Library.fs

Ketiga fungsi pada modul Perhitungan murni, yaitu menerima daftar nilai dan mengembalikan hasil perhitungan tanpa efek samping. Bentuk seperti ini yang kelak diuji dengan Expecto dan FsCheck. Penempatan fungsi di proyek pustaka sejak awal membuat pengujian tidak perlu menyentuh kode antarmuka.

Solusi multi proyek#

Dua proyek yang terpisah akan sulit dikelola satu per satu karena setiap perintah build harus menyebut masing-masing proyek. Solusi (solution) memecahkan hal itu dengan merangkai beberapa proyek dalam satu berkas, sehingga build dan pengujian dapat menyasar semuanya sekaligus. Berkas solusi dibuat dengan dotnet new sln, lalu proyek didaftarkan satu per satu dengan dotnet sln add.

bash
# membuat berkas solusi untuk merangkai kedua proyek
dotnet new sln -n StatistikNilai
# mendaftarkan kedua proyek ke dalam solusi
dotnet sln add StatistikNilai/StatistikNilai.fsproj IntiNilai/IntiNilai.fsproj
text Keluaran
The template "Solution File" was created successfully.

Project `StatistikNilai/StatistikNilai.fsproj` added to the solution.
Project `IntiNilai/IntiNilai.fsproj` added to the solution.

Kedua perintah di atas menghasilkan berkas StatistikNilai.slnx di folder kerja. Isinya berupa daftar proyek yang telah didaftarkan. Setiap proyek muncul sebagai elemen Project dengan atribut Path yang menunjuk ke berkas fsproj-nya.

xml Isi StatistikNilai.slnx setelah kedua proyek didaftarkan
<Solution>
  <Project Path="IntiNilai/IntiNilai.fsproj" />
  <Project Path="StatistikNilai/StatistikNilai.fsproj" />
</Solution>

Referensi antar proyek#

Proyek konsol perlu memanggil fungsi dari IntiNilai, dan hubungan itu dinyatakan lewat referensi proyek (project reference). Perintah dotnet add reference menambahkan referensi tersebut pada berkas fsproj proyek pemakai.

bash
# menghubungkan proyek konsol ke proyek pustaka
dotnet add StatistikNilai reference IntiNilai/IntiNilai.fsproj
text Keluaran
Reference `..\IntiNilai\IntiNilai.fsproj` added to the project.
xml ProjectReference yang ditambahkan ke StatistikNilai.fsproj
  <ItemGroup>
    <ProjectReference Include="..\IntiNilai\IntiNilai.fsproj" />
  </ItemGroup>

Elemen ProjectReference membuat StatistikNilai bergantung pada IntiNilai. Saat proyek konsol dibangun, proyek pustaka dibangun lebih dahulu, kemudian hasil kompilasinya dirujuk oleh proyek pemakai. Pesan perintah dan isi fsproj keduanya menuliskan jalur dengan pemisah ala Windows pada semua sistem operasi, bentuk yang memang digunakan MSBuild secara internal. Arah referensinya satu arah, yaitu dari konsol ke pustaka, sehingga pustaka tidak mengetahui aplikasi pemakainya dan tetap dapat digunakan proyek lain.

Build dan run dari CLI#

Berkas Program.fs milik templat kini diganti dengan program yang menghitung rekap nilai ujian lewat fungsi pustaka.

fsharp Program.fs
// tidak-dijalankan: berkas proyek ini dieksekusi melalui blok bash berikutnya
// rekap nilai ujian memanggil fungsi dari proyek pustaka IntiNilai
open IntiNilai

let nilai = [ 80; 45; 75; 90; 60 ]

printfn "Rata-rata      : %.1f" (Perhitungan.rataRata nilai)
printfn "Nilai maksimum : %d" (Perhitungan.maksimum nilai)
printfn "Nilai minimum  : %d" (Perhitungan.minimum nilai)
bash
# mengisi berkas Program.fs proyek konsol dari berkas yang ditampilkan di atas
cp Program.fs StatistikNilai/Program.fs
# membangun seluruh proyek yang terdaftar pada solusi
dotnet build StatistikNilai.slnx

Perintah dotnet build memulihkan paket bila diperlukan, kemudian mengompilasi kedua proyek mengikuti urutan referensinya. Keluarannya memuat jalur absolut folder build dan durasi yang berubah pada setiap eksekusi, sehingga keluarannya tidak ditempelkan pada topik ini. Build yang berhasil diakhiri pesan Build succeeded dengan hitungan warning dan error nol.

bash
# menjalankan proyek konsol; program memanggil fungsi dari pustaka
dotnet run --project StatistikNilai
text Keluaran
Rata-rata      : 70.0
Nilai maksimum : 90
Nilai minimum  : 45

Rata-rata 70.0 berasal dari jumlah 350 yang dibagi lima nilai, sedangkan nilai maksimum dan minimum langsung dibaca dari daftar. Opsi --project menunjuk proyek yang dieksekusi, sehingga perintah dapat dijalankan dari folder solusi tanpa masuk ke folder proyek terlebih dahulu. Eksekusi berikutnya jauh lebih cepat karena hanya berkas yang berubah yang dikompilasi ulang.

Kesalahan umum#

  • Berkas .fs baru tidak ikut dikompilasi. Berbeda dari SDK C# yang menyertakan seluruh berkas .cs di folder secara otomatis, SDK F# hanya mengompilasi berkas yang terdaftar pada elemen Compile Include di fsproj. Berkas kode baru wajib didaftarkan pada berkas tersebut, dan perubahannya baru terlihat setelah proyek dibangun ulang.
  • Urutan berkas pada Compile Include terbalik. F# mengompilasi berkas satu per satu mengikuti urutan daftarnya, sehingga berkas yang menggunakan tipe atau modul dari berkas lain harus dituliskan setelah berkas pemiliknya. Apabila urutannya terbalik, compiler melaporkan error pengenal tidak dikenal, dan perbaikannya dengan menaikkan berkas pemilik tipe ke posisi yang lebih atas.

Ringkasan#

  • Berkas fsproj mendeklarasikan SDK, TargetFramework net10.0, OutputType, dan daftar berkas kode yang dikompilasi sesuai urutannya.
  • dotnet new membuat proyek, sedangkan dotnet new sln dan dotnet sln add merangkai beberapa proyek ke dalam satu solusi .slnx.
  • dotnet add reference menuliskan ProjectReference yang menyatakan ketergantungan proyek konsol pada proyek pustaka.
  • dotnet build mengompilasi seluruh proyek dalam solusi, sedangkan dotnet run --project mengeksekusi satu proyek dari folder solusi.

Referensi#