Skip to content

Commit

Permalink
refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
konoui committed May 13, 2024
1 parent dcc096b commit 30a6b7d
Show file tree
Hide file tree
Showing 53 changed files with 1,958 additions and 1,175 deletions.
6 changes: 4 additions & 2 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ builds:
goarch:
- amd64
- arm64
flags:
- -trimpath
ldflags:
- -s -w
- -X main.Version={{.Version}}
- -X main.Revision={{.ShortCommit}}
- -X github.com/konoui/lipo/cmd.Version={{.Version}}
- -X github.com/konoui/lipo/cmd.Revision={{.ShortCommit}}
env:
- CGO_ENABLED=0
archives:
Expand Down
9 changes: 6 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ SRC_DIR := ./
BIN_NAME := lipo
BINARY := bin/$(BIN_NAME)

GOLANGCI_LINT_VERSION := v1.52.2
GOLANGCI_LINT_VERSION := v1.58.1
export GO111MODULE=on

CMD_PACKAGE_DIR := github.com/konoui/lipo/cmd
LDFLAGS := -X '$(CMD_PACKAGE_DIR).Version=$(VERSION)' -X '$(CMD_PACKAGE_DIR).Revision=$(REVISION)'
CMD_PACKAGE := github.com/konoui/lipo/cmd
LDFLAGS := -X '$(CMD_PACKAGE).Version=$(VERSION)' -X '$(CMD_PACKAGE).Revision=$(REVISION)'

## Build binaries on your environment
build:
Expand All @@ -27,6 +27,9 @@ test-large-file:
test-on-non-macos:
./test-on-non-macos.sh

release-test:
goreleaser --snapshot --skip-publish --rm-dist

cover:
go test -coverpkg=./... -coverprofile=cover.out ./...
go tool cover -html=cover.out -o cover.html
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module github.com/konoui/lipo

go 1.20
go 1.22

require github.com/konoui/go-qsort v0.1.0
7 changes: 7 additions & 0 deletions pkg/ar/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
create-archives:
cd testdata/ &&\
libtool -c -static arm64-func1.o -o arm64-func1.a &&\
libtool -c -static arm64-func1.o arm64-func2.o -o arm64-func12.a &&\
libtool -c -static arm64-func1.o arm64-func2.o arm64-func3.o -o arm64-func123.a &&\
libtool -c -static arm64-func1.o amd64-func1.o -o fat-arm64-amd64-func1 &&\
ar -rc arm64-amd64-func12.a amd64-func1.o amd64-func2.o arm64-func1.o arm64-func2.o
175 changes: 175 additions & 0 deletions pkg/ar/ar.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package ar

import (
"bytes"
"errors"
"fmt"
"io"
"strconv"
"strings"
)

const (
headerSize = 60
PrefixSymdef = "__.SYMDEF"
)

var (
MagicHeader = []byte("!<arch>\n")
ErrInvalidFormat = errors.New("not ar file format")
)

type File struct {
*io.SectionReader
Header
}

// https://en.wikipedia.org/wiki/Ar_(Unix)
// TODO other fields
type Header struct {
Name string
Size int64
nameSize int64
}

type Reader struct {
sr *io.SectionReader
cur int64
next int64
}

// NewArchive is a wrapper
func NewArchive(ra io.ReaderAt) ([]*File, error) {
r, err := NewReader(ra)
if err != nil {
return nil, err
}

files := []*File{}

for {
f, err := r.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}

files = append(files, f)
}
return files, nil
}

func NewReader(r io.ReaderAt) (*Reader, error) {
mhLen := len(MagicHeader)
buf := make([]byte, mhLen)
sr := io.NewSectionReader(r, 0, 1<<63-1)
if _, err := io.ReadFull(sr, buf); err != nil {
if errors.Is(err, io.EOF) {
return nil, ErrInvalidFormat
}
return nil, err
}

if !bytes.Equal(MagicHeader, buf) {
return nil, fmt.Errorf("invalid magic header want: %s, got: %s: %w",
string(MagicHeader), string(buf), ErrInvalidFormat)
}

return &Reader{sr: sr, cur: int64(mhLen), next: int64(mhLen)}, nil
}

// Next returns a file header and a reader of original data
func (r *Reader) Next() (*File, error) {
hdr, err := r.readHeader()
if err != nil {
return nil, err
}

sr := io.NewSectionReader(r.sr, r.cur+hdr.nameSize, hdr.Size-hdr.nameSize)

r.cur += hdr.Size
return &File{
SectionReader: sr,
Header: *hdr,
}, nil
}

func (r *Reader) readHeader() (*Header, error) {
if _, err := r.sr.Seek(r.next, io.SeekStart); err != nil {
return nil, err
}

header := make([]byte, headerSize)
n, err := io.ReadFull(r.sr, header)
if err != nil {
return nil, err
}

if n != headerSize {
return nil, fmt.Errorf("error reading header want: %d bytes, got: %d bytes", headerSize, n)
}

name := TrimTailSpace(header[0:16])
// mTime := header[16:18]
// uid, gid := header[28:34], buf[34:40]
// perm := header[40:48]
size, err := strconv.ParseInt(TrimTailSpace(header[48:58]), 10, 64)
if err != nil {
return nil, fmt.Errorf("parse size value of name: %v", err)
}

endChars := header[58:60]
if want := []byte{0x60, 0x0a}; !bytes.Equal(want, endChars) {
return nil, fmt.Errorf("unexpected ending characters want: %x, got: %x", want, endChars)
}

// update
r.cur += headerSize

var nameSize int64 = 0
// handle BSD variant
if strings.HasPrefix(name, "#1/") {
trimmedSize := strings.TrimPrefix(name, "#1/")
parsedSize, err := strconv.ParseInt(trimmedSize, 10, 64)
if err != nil {
return nil, err
}

nameBuf := make([]byte, parsedSize)
if _, err := io.ReadFull(r.sr, nameBuf); err != nil {
return nil, err
}

// update
name = strings.TrimRight(string(nameBuf), "\x00")
// update
nameSize = int64(parsedSize)
}

// align to read body
if size%2 != 0 {
if _, err := io.CopyN(io.Discard, r.sr, 1); err != nil {
if err != io.EOF {
return nil, err
}
}
// update
r.cur += 1
}

// next offset points to a next header
r.next = r.cur + size

h := &Header{
Size: size,
Name: name,
nameSize: nameSize,
}
return h, nil
}

func TrimTailSpace(b []byte) string {
return strings.TrimRight(string(b), " ")
}
148 changes: 148 additions & 0 deletions pkg/ar/ar_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package ar_test

import (
"bytes"
"crypto/sha256"
"debug/macho"
"encoding/hex"
"io"
"os"
"path/filepath"
"reflect"
"strings"
"testing"

"github.com/konoui/lipo/pkg/ar"
)

func TestNew(t *testing.T) {
tests := []struct {
name string
filename string
want []string
}{
{
name: "arm64-func1.a",
filename: "arm64-func1.a",
want: []string{"arm64-func1.o"},
},
{
name: "arm64-func12.a",
filename: "arm64-func12.a",
want: []string{"arm64-func1.o", "arm64-func2.o"},
},
{
name: "arm64-func123.a",
filename: "arm64-func123.a",
want: []string{"arm64-func1.o", "arm64-func2.o", "arm64-func3.o"},
},
{
name: "arm64-amd64-func12.a",
filename: "arm64-amd64-func12.a",
want: []string{"amd64-func1.o", "amd64-func2.o", "arm64-func1.o", "arm64-func2.o"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := filepath.Join("testdata", tt.filename)
f, err := os.Open(p)
if err != nil {
t.Fatal(err)
}
defer f.Close()

archiver, err := ar.NewReader(f)
if err != nil {
t.Fatal(err)
}

got := []string{}
for {
file, err := archiver.Next()
if err == io.EOF {
break
}
if err != nil {
t.Fatal(err)
}
if strings.HasPrefix(file.Name, ar.PrefixSymdef) {
continue
}

got = append(got, file.Name)

machoBuf := &bytes.Buffer{}
tee := io.TeeReader(file, machoBuf)
gotHash := sha256String(t, tee)
wantHash := sha256StringFromFile(t, filepath.Join("testdata", file.Name))
if gotHash != wantHash {
t.Errorf("%s: want: %s got: %s", file.Name, wantHash, gotHash)
}

// validate
if _, err := macho.NewFile(bytes.NewReader(machoBuf.Bytes())); err != nil {
t.Fatal(err)
}
}

if !reflect.DeepEqual(tt.want, got) {
t.Errorf("want: %v, got: %v", tt.want, got)
}
})
}
}

func sha256StringFromFile(t *testing.T, p string) string {
f, err := os.Open(p)
if err != nil {
t.Fatal(err)
}
defer f.Close()
return sha256String(t, f)
}

func sha256String(t *testing.T, r io.Reader) string {
h := sha256.New()
_, err := io.Copy(h, r)
if err != nil {
t.Fatal(err)
}

return hex.EncodeToString(h.Sum(nil))
}

func Test_TrimTailSpace(t *testing.T) {
tests := []struct {
name string
input []byte
want string
}{
{
name: "space1",
input: []byte(" test"),
want: " test",
},
{
name: "space2",
input: []byte(" test "),
want: " test",
},
{
name: "space3",
input: []byte("test test "),
want: "test test",
},
{
name: "space4",
input: []byte(" test test "),
want: " test test",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ar.TrimTailSpace(tt.input); got != tt.want {
t.Errorf("TrimTailSpace() = [%v:, want [%v]", got, tt.want)
}
})
}
}
Binary file added pkg/ar/testdata/amd64-func1.o
Binary file not shown.
Binary file added pkg/ar/testdata/amd64-func2.o
Binary file not shown.
Binary file added pkg/ar/testdata/arm64-amd64-func12.a
Binary file not shown.
Binary file added pkg/ar/testdata/arm64-func1.a
Binary file not shown.
Binary file added pkg/ar/testdata/arm64-func1.o
Binary file not shown.
Binary file added pkg/ar/testdata/arm64-func12.a
Binary file not shown.
Binary file added pkg/ar/testdata/arm64-func123.a
Binary file not shown.
Binary file added pkg/ar/testdata/arm64-func2.o
Binary file not shown.
Binary file added pkg/ar/testdata/arm64-func3.o
Binary file not shown.
Binary file added pkg/ar/testdata/fat-arm64-amd64-func1
Binary file not shown.
6 changes: 6 additions & 0 deletions pkg/ar/testdata/func1.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include <stdio.h>

int func1() {
printf("func1");
return 0;
}
6 changes: 6 additions & 0 deletions pkg/ar/testdata/func2.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include <stdio.h>

int func2() {
printf("func2");
return 0;
}
Loading

0 comments on commit 30a6b7d

Please sign in to comment.