-
Notifications
You must be signed in to change notification settings - Fork 572
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
263 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
name: Build @ironfish binaries | ||
|
||
on: | ||
release: | ||
types: | ||
- published | ||
push | ||
# on: | ||
# release: | ||
# types: | ||
# - published | ||
|
||
jobs: | ||
build: | ||
|
@@ -50,18 +52,29 @@ jobs: | |
uses: actions/setup-node@v4 | ||
with: | ||
node-version: 18 | ||
|
||
- name: Set up Go | ||
uses: actions/setup-go@v4 | ||
with: | ||
go-version: '1.20.6' | ||
|
||
- name: Checkout repository | ||
uses: actions/checkout@v4 | ||
|
||
- name: npm init | ||
run: npm init -y | ||
|
||
- name: install dependencies | ||
run: npm install ironfish [email protected] | ||
|
||
- name: caxa package | ||
id: caxa | ||
- name: create build.tar.gz for binary | ||
id: build | ||
run: | | ||
mkdir build | ||
cd build | ||
npm init -y | ||
npm install ironfish | ||
tar -czf ../build.tar.gz -C . . | ||
- name: create binary | ||
id: binary | ||
run: | | ||
npx caxa --uncompression-message "Running the CLI for the first time may take a while, please wait..." --input . --output "${{ matrix.settings.system != 'windows' && 'ironfish' || 'ironfish.exe' }}" -- "{{caxa}}/node_modules/.bin/node" "--enable-source-maps" "{{caxa}}/node_modules/ironfish/bin/run" | ||
echo "RELEASE_NAME=ironfish-${{ matrix.settings.system }}-${{ matrix.settings.arch }}-${{ github.event.release.tag_name }}.zip" | ||
ls | ||
go build -ldflags "-X 'main.Identifier=identifier' -X 'main.Command={{caxac}}/node --enable-source-maps {{caxac}}/node_modules/ironfish/bin/run' -X 'main.UncompressionMessage=Unpackaging ironfish application, this may take a minute when run for the first time.'" tools/build-binary.go | ||
- name: set paths | ||
id: set_paths | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
package main | ||
|
||
import ( | ||
"archive/tar" | ||
"compress/gzip" | ||
"context" | ||
"embed" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"log" | ||
"os" | ||
"os/exec" | ||
"path" | ||
"path/filepath" | ||
"regexp" | ||
"strconv" | ||
"strings" | ||
"time" | ||
) | ||
|
||
// When building this file, a build.tar.gz should be present and will be embedded in the binary | ||
|
||
//go:embed build.tar.gz | ||
Check failure on line 24 in tools/build-binary.go GitHub Actions / Build apple x86_64
Check failure on line 24 in tools/build-binary.go GitHub Actions / Build windows x86_64
|
||
var data embed.FS | ||
|
||
var ( | ||
Command string | ||
UncompressionMessage string | ||
) | ||
|
||
func main() { | ||
|
||
var applicationDirectory string | ||
for extractionAttempt := 0; true; extractionAttempt++ { | ||
lock := path.Join(os.TempDir(), "caxac/locks", strconv.Itoa(extractionAttempt)) | ||
applicationDirectory = path.Join(os.TempDir(), "caxac/applications", strconv.Itoa(extractionAttempt)) | ||
applicationDirectoryFileInfo, err := os.Stat(applicationDirectory) | ||
if err != nil && !errors.Is(err, os.ErrNotExist) { | ||
log.Fatalf("caxac stub: Failed to find information about the application directory: %v", err) | ||
} | ||
if err == nil && !applicationDirectoryFileInfo.IsDir() { | ||
log.Fatalf("caxac stub: Path to application directory already exists and isn’t a directory: %v", err) | ||
} | ||
if err == nil && applicationDirectoryFileInfo.IsDir() { | ||
lockFileInfo, err := os.Stat(lock) | ||
if err != nil && !errors.Is(err, os.ErrNotExist) { | ||
log.Fatalf("caxac stub: Failed to find information about the lock: %v", err) | ||
} | ||
if err == nil && !lockFileInfo.IsDir() { | ||
log.Fatalf("caxac stub: Path to lock already exists and isn’t a directory: %v", err) | ||
} | ||
if err == nil && lockFileInfo.IsDir() { | ||
// Application directory exists and lock exists as well, so a previous extraction wasn’t successful or an extraction is happening right now and hasn’t finished yet, in either case, start over with a fresh name. | ||
continue | ||
} | ||
if err != nil && errors.Is(err, os.ErrNotExist) { | ||
// Application directory exists and lock doesn’t exist, so a previous extraction was successful. Use the cached version of the application directory and don’t extract again. | ||
break | ||
} | ||
} | ||
if err != nil && errors.Is(err, os.ErrNotExist) { | ||
ctx, cancelCtx := context.WithCancel(context.Background()) | ||
if UncompressionMessage != "" { | ||
fmt.Fprint(os.Stderr, UncompressionMessage) | ||
go func() { | ||
ticker := time.NewTicker(time.Second * 5) | ||
defer ticker.Stop() | ||
for { | ||
select { | ||
case <-ticker.C: | ||
fmt.Fprint(os.Stderr, ".") | ||
case <-ctx.Done(): | ||
fmt.Fprintln(os.Stderr, "") | ||
return | ||
} | ||
} | ||
}() | ||
} | ||
|
||
if err := os.MkdirAll(lock, 0755); err != nil { | ||
log.Fatalf("caxac stub: Failed to create the lock directory: %v", err) | ||
} | ||
|
||
embeddedDataReader, err := data.Open("build.tar.gz") | ||
if err != nil { | ||
log.Fatalf("Failed to open embedded data: %v", err) | ||
} | ||
defer embeddedDataReader.Close() | ||
|
||
if err := Untar(embeddedDataReader, applicationDirectory); err != nil { | ||
log.Fatalf("caxac stub: Failed to uncompress embedded data: %v", err) | ||
} | ||
|
||
os.Remove(lock) | ||
|
||
cancelCtx() | ||
break | ||
} | ||
} | ||
splitCommand := strings.Split(Command, " ") | ||
expandedCommand := make([]string, len(splitCommand)) | ||
applicationDirectoryPlaceholderRegexp := regexp.MustCompile(`\{\{\s*caxac\s*\}\}`) | ||
for key, commandPart := range splitCommand { | ||
expandedCommand[key] = applicationDirectoryPlaceholderRegexp.ReplaceAllLiteralString(commandPart, applicationDirectory) | ||
} | ||
|
||
command := exec.Command(expandedCommand[0], append(expandedCommand[1:], os.Args[1:]...)...) | ||
command.Stdin = os.Stdin | ||
command.Stdout = os.Stdout | ||
command.Stderr = os.Stderr | ||
err := command.Run() | ||
var exitError *exec.ExitError | ||
if errors.As(err, &exitError) { | ||
os.Exit(exitError.ExitCode()) | ||
} else if err != nil { | ||
log.Fatalf("caxac stub: Failed to run command: %v", err) | ||
} | ||
} | ||
|
||
// | ||
// Adapted from https://github.com/leafac/caxa and https://github.com/golang/build/blob/db2c93053bcd6b944723c262828c90af91b0477a/internal/untar/untar.go and https://github.com/mholt/archiver/tree/v3.5.0 | ||
|
||
// Untar reads the gzip-compressed tar file from r and writes it into dir. | ||
func Untar(r io.Reader, dir string) error { | ||
return untar(r, dir) | ||
} | ||
|
||
func untar(r io.Reader, dir string) (err error) { | ||
t0 := time.Now() | ||
nFiles := 0 | ||
madeDir := map[string]bool{} | ||
zr, err := gzip.NewReader(r) | ||
if err != nil { | ||
return fmt.Errorf("requires gzip-compressed body: %v", err) | ||
} | ||
tr := tar.NewReader(zr) | ||
loggedChtimesError := false | ||
for { | ||
f, err := tr.Next() | ||
if err == io.EOF { | ||
break | ||
} | ||
if err != nil { | ||
return fmt.Errorf("tar error: %v", err) | ||
} | ||
if !validRelPath(f.Name) { | ||
return fmt.Errorf("tar contained invalid name error %q", f.Name) | ||
} | ||
rel := filepath.FromSlash(f.Name) | ||
abs := filepath.Join(dir, rel) | ||
|
||
fi := f.FileInfo() | ||
mode := fi.Mode() | ||
switch { | ||
case mode.IsRegular(): | ||
// Make the directory. This is redundant because it should | ||
// already be made by a directory entry in the tar | ||
// beforehand. Thus, don't check for errors; the next | ||
// write will fail with the same error. | ||
dir := filepath.Dir(abs) | ||
if !madeDir[dir] { | ||
if err := os.MkdirAll(filepath.Dir(abs), 0755); err != nil { | ||
return err | ||
} | ||
madeDir[dir] = true | ||
} | ||
wf, err := os.OpenFile(abs, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode.Perm()) | ||
if err != nil { | ||
return err | ||
} | ||
n, err := io.Copy(wf, tr) | ||
if closeErr := wf.Close(); closeErr != nil && err == nil { | ||
err = closeErr | ||
} | ||
if err != nil { | ||
return fmt.Errorf("error writing to %s: %v", abs, err) | ||
} | ||
if n != f.Size { | ||
return fmt.Errorf("only wrote %d bytes to %s; expected %d", n, abs, f.Size) | ||
} | ||
modTime := f.ModTime | ||
if modTime.After(t0) { | ||
// Clamp modtimes at system time. See | ||
// golang.org/issue/19062 when clock on | ||
// buildlet was behind the gitmirror server | ||
// doing the git-archive. | ||
modTime = t0 | ||
} | ||
if !modTime.IsZero() { | ||
if err := os.Chtimes(abs, modTime, modTime); err != nil && !loggedChtimesError { | ||
// benign error. Gerrit doesn't even set the | ||
// modtime in these, and we don't end up relying | ||
// on it anywhere (the gomote push command relies | ||
// on digests only), so this is a little pointless | ||
// for now. | ||
// log.Printf("error changing modtime: %v (further Chtimes errors suppressed)", err) | ||
loggedChtimesError = true // once is enough | ||
} | ||
} | ||
nFiles++ | ||
case mode.IsDir(): | ||
if err := os.MkdirAll(abs, 0755); err != nil { | ||
return err | ||
} | ||
madeDir[abs] = true | ||
case f.Typeflag == tar.TypeSymlink: | ||
// leafac: Added by me to support symbolic links. Adapted from https://github.com/mholt/archiver/blob/v3.5.0/tar.go#L254-L276 and https://github.com/mholt/archiver/blob/v3.5.0/archiver.go#L313-L332 | ||
err := os.MkdirAll(filepath.Dir(abs), 0755) | ||
if err != nil { | ||
return fmt.Errorf("%s: making directory for file: %v", abs, err) | ||
} | ||
_, err = os.Lstat(abs) | ||
if err == nil { | ||
err = os.Remove(abs) | ||
if err != nil { | ||
return fmt.Errorf("%s: failed to unlink: %+v", abs, err) | ||
} | ||
} | ||
|
||
err = os.Symlink(f.Linkname, abs) | ||
if err != nil { | ||
return fmt.Errorf("%s: making symbolic link for: %v", abs, err) | ||
} | ||
default: | ||
return fmt.Errorf("tar file entry %s contained unsupported file type %v", f.Name, mode) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func validRelPath(p string) bool { | ||
if p == "" || strings.Contains(p, `\`) || strings.HasPrefix(p, "/") || strings.Contains(p, "../") { | ||
return false | ||
} | ||
return true | ||
} |