embed ledger binary inside paisa

This commit is contained in:
Anantha Kumaran 2023-09-09 11:53:36 +05:30
parent 3a740952a6
commit 27165ea4a4
6 changed files with 239 additions and 23 deletions

View File

@ -9,9 +9,12 @@ on:
jobs:
linux-binary:
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2.4.0
- uses: cachix/install-nix-action@v22
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: actions/setup-node@v3
with:
node-version: 18
@ -22,6 +25,8 @@ jobs:
sudo apt-get install -y sqlite3
npm install
npm run build
nix-build ledger.nix
cp ./result/bin/ledger internal/binary/ledger
go build
cp paisa paisa-linux-amd64
- name: Release
@ -33,11 +38,13 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
mac-binary:
runs-on: macos-latest
runs-on: macos-11
steps:
- uses: actions/checkout@v2.4.0
- run: |
brew install --force --overwrite go sqlite3
brew install --build-from-source --verbose ./ledger.rb
cp "$(brew --prefix ledger)/bin/ledger" internal/binary/ledger
npm install
npm run build
go build
@ -51,7 +58,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
windows-binary:
runs-on: windows-latest
runs-on: windows-2019
steps:
- uses: actions/checkout@v2.4.0
- uses: actions/setup-node@v3
@ -62,8 +69,11 @@ jobs:
go-version: '1.20.5'
- run: |
choco install sqlite
choco install wget
npm install
npm run build
wget https://github.com/FullofQuarks/Windows-Ledger-Binaries/releases/download/v3.3.2/ledger.exe
cp ledger.exe internal/binary/ledger
go build
cp paisa.exe paisa-windows-amd64.exe
- name: Release

111
internal/binary/embed.go Normal file
View File

@ -0,0 +1,111 @@
package binary
import (
_ "embed"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
log "github.com/sirupsen/logrus"
)
var cachedLedgerBinaryPath string
func LedgerBinaryPath() string {
if cachedLedgerBinaryPath != "" {
return cachedLedgerBinaryPath
}
path, err := exec.LookPath("ledger")
if err == nil {
cachedLedgerBinaryPath = path
return path
}
cacheDir, err := os.UserCacheDir()
if err != nil {
log.Fatal(err)
}
binDir := filepath.Join(cacheDir, "paisa")
binaryPath := "ledger"
if runtime.GOOS == "windows" {
binaryPath += ".exe"
}
path = filepath.Join(binDir, binaryPath)
err = stage(path, ledgerBinary, 0750)
if err != nil {
log.Fatal(err)
}
cachedLedgerBinaryPath = path
return path
}
//go:embed ledger
var ledgerBinary []byte
// EmbeddedBinaryNeedsUpdate returns true if the provided embedded binary file should
// be updated. This determination is based on the modification times and file sizes of both
// the provided executable and the embedded executable. It is expected that the embedded binary
// modification times should match the main `paisa` executable.
func embeddedBinaryNeedsUpdate(exinfo os.FileInfo, embeddedBinaryPath string, size int64) bool {
if pathinfo, err := os.Stat(embeddedBinaryPath); err == nil {
return !exinfo.ModTime().Equal(pathinfo.ModTime()) || pathinfo.Size() != size
}
// If the stat fails, the file is either missing or permissions are missing
// to read this -- let above know that an update should be attempted.
return true
}
// Stage ...
func stage(p string, binData []byte, filemode os.FileMode) error {
log.Debugf("Staging '%s'", p)
err := os.MkdirAll(filepath.Dir(p), filemode)
if err != nil {
return fmt.Errorf("failed to create dir '%s': %w", filepath.Dir(p), err)
}
selfexe, err := os.Executable()
if err != nil {
return fmt.Errorf("unable to determine current executable: %w", err)
}
exinfo, err := os.Stat(selfexe)
if err != nil {
return fmt.Errorf("unable to stat '%s': %w", selfexe, err)
}
if !embeddedBinaryNeedsUpdate(exinfo, p, int64(len(binData))) {
log.Debug("Re-use existing file:", p)
return nil
}
infile, err := os.Open(selfexe)
if err != nil {
return fmt.Errorf("unable to open executable '%s': %w", selfexe, err)
}
defer infile.Close()
log.Debugf("Writing static file: '%s'", p)
_ = os.Remove(p)
err = os.WriteFile(p, binData, 0550)
if err != nil {
return fmt.Errorf("unable to copy to '%s': %w", p, err)
}
// In order to properly determine if an update of an embedded binary file is needed,
// the staged embedded binary needs to have the same modification time as the `paisa`
// executable.
if err := os.Chtimes(p, exinfo.ModTime(), exinfo.ModTime()); err != nil {
return fmt.Errorf("failed to set file modification times of '%s': %w", p, err)
}
return nil
}

1
internal/binary/ledger Normal file
View File

@ -0,0 +1 @@
// DUMMY FILE THAT WILL GET REPLACED DURING RELEASE

View File

@ -20,6 +20,7 @@ import (
"encoding/json"
"github.com/ananthakumaran/paisa/internal/binary"
"github.com/ananthakumaran/paisa/internal/config"
"github.com/ananthakumaran/paisa/internal/model/posting"
"github.com/ananthakumaran/paisa/internal/model/price"
@ -52,16 +53,12 @@ func Cli() Ledger {
func (LedgerCLI) ValidateFile(journalPath string) ([]LedgerFileError, string, error) {
errors := []LedgerFileError{}
_, err := exec.LookPath("ledger")
if err != nil {
log.Fatal(err)
}
command := exec.Command("ledger", "-f", journalPath, "balance")
command := exec.Command(binary.LedgerBinaryPath(), "-f", journalPath, "balance")
var output, error bytes.Buffer
command.Stdout = &output
command.Stderr = &error
err = command.Run()
err := command.Run()
if err == nil {
return errors, output.String(), nil
}
@ -80,12 +77,7 @@ func (LedgerCLI) ValidateFile(journalPath string) ([]LedgerFileError, string, er
func (LedgerCLI) Parse(journalPath string, _prices []price.Price) ([]*posting.Posting, error) {
var postings []*posting.Posting
_, err := exec.LookPath("ledger")
if err != nil {
log.Fatal(err)
}
postings, err = execLedgerCommand(journalPath, []string{})
postings, err := execLedgerCommand(journalPath, []string{})
if err != nil {
return nil, err
@ -106,16 +98,11 @@ func (LedgerCLI) Parse(journalPath string, _prices []price.Price) ([]*posting.Po
func (LedgerCLI) Prices(journalPath string) ([]price.Price, error) {
var prices []price.Price
_, err := exec.LookPath("ledger")
if err != nil {
log.Fatal(err)
}
command := exec.Command("ledger", "-f", journalPath, "pricesdb")
command := exec.Command(binary.LedgerBinaryPath(), "-f", journalPath, "pricesdb")
var output, error bytes.Buffer
command.Stdout = &output
command.Stderr = &error
err = command.Run()
err := command.Run()
if err != nil {
return prices, err
}
@ -280,7 +267,7 @@ func execLedgerCommand(journalPath string, flags []string) ([]*posting.Posting,
args := append(append([]string{"-f", journalPath}, flags...), "csv", "--csv-format", "%(quoted(date)),%(quoted(payee)),%(quoted(display_account)),%(quoted(commodity(scrub(display_amount)))),%(quoted(quantity(scrub(display_amount)))),%(quoted(scrub(market(amount,date,'"+config.DefaultCurrency()+"') * 100000000))),%(quoted(xact.filename)),%(quoted(xact.id)),%(quoted(cleared ? \"*\" : (pending ? \"!\" : \"\"))),%(quoted(tag('Recurring'))),%(quoted(xact.beg_line)),%(quoted(xact.end_line))\n")
command := exec.Command("ledger", args...)
command := exec.Command(binary.LedgerBinaryPath(), args...)
var output, error bytes.Buffer
command.Stdout = &output
command.Stderr = &error

44
ledger.nix Normal file
View File

@ -0,0 +1,44 @@
{ pkgs ? import <nixpkgs> { } }:
with pkgs;
pkgsStatic.stdenv.mkDerivation {
pname = "ledger";
version = "3.3.2";
src = fetchFromGitHub {
owner = "ledger";
repo = "ledger";
rev = "4355c4faf157d5ef47b126286aa501742732708d";
hash = "sha256-9IowdrQpJarALr21Y+Mhmld+eC4YUpEn+goqWyBb6Xc=";
};
outputs = [ "out" "dev" ];
buildInputs = [ pkgsStatic.gmp pkgsStatic.mpfr gnused pkgsStatic.boost ];
nativeBuildInputs = [ cmake tzdata ];
cmakeFlags = [
"-DCMAKE_INSTALL_LIBDIR=lib"
"-DBUILD_DOCS:BOOL=OFF"
"-DUSE_PYTHON:BOOL=OFF"
"-DUSE_GPGME:BOOL=OFF"
"-DBUILD_LIBRARY:BOOL=OFF"
];
enableParallelBuilding = true;
installTargets = [ "install" ];
checkPhase = ''
runHook preCheck
env LD_LIBRARY_PATH=$PWD \
DYLD_LIBRARY_PATH=$PWD \
ctest -j$NIX_BUILD_CORES
runHook postCheck
'';
doCheck = true;
}

63
ledger.rb Normal file
View File

@ -0,0 +1,63 @@
class Ledger < Formula
desc "Command-line, double-entry accounting tool"
homepage "https://ledger-cli.org/"
url "https://github.com/ledger/ledger/archive/v3.3.2.tar.gz"
sha256 "555296ee1e870ff04e2356676977dcf55ebab5ad79126667bc56464cb1142035"
license "BSD-3-Clause"
revision 1
head "https://github.com/ledger/ledger.git", branch: "master"
livecheck do
url :stable
regex(/^v?(\d+(?:\.\d+)+)$/i)
end
depends_on "boost"
depends_on "gmp"
depends_on "mpfr"
depends_on "python@3.11" => :build
depends_on "cmake" => :build
patch :DATA
def install
ENV.cxx11
ENV.prepend_path "PATH", Formula["python@3.11"].opt_libexec/"bin"
args = %W[
--jobs=#{ENV.make_jobs}
--output=build
--prefix=#{prefix}
--boost=#{Formula["boost"].opt_prefix}
--
-DCMAKE_FIND_LIBRARY_SUFFIXES=.a
-DBUILD_SHARED_LIBS:BOOL=OFF
-DBUILD_DOCS:BOOL=OFF
-DBoost_NO_BOOST_CMAKE=ON
-DUSE_PYTHON:BOOL=OFF
-DUSE_GPGME:BOOL=OFF
-DBUILD_LIBRARY:BOOL=OFF
-DBoost_USE_STATIC_LIBS:BOOL=ON
] + std_cmake_args
system "./acprep", "make", *args
system "./acprep", "make", "install", *args
end
end
__END__
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 83a6f89d..e84be891 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -274,8 +274,8 @@ find_opt_library_and_header(EDIT_PATH histedit.h EDIT_LIB edit HAVE_EDIT)
########################################################################
macro(add_ledger_library_dependencies _target)
- target_link_libraries(${_target} ${MPFR_LIB})
- target_link_libraries(${_target} ${GMP_LIB})
+ target_link_libraries(${_target} /usr/local/lib/libmpfr.a)
+ target_link_libraries(${_target} /usr/local/lib/libgmp.a)
if (HAVE_EDIT)
target_link_libraries(${_target} ${EDIT_LIB})
endif()