release-v1

This commit is contained in:
Uday Hiwarale 2020-04-12 00:59:35 +05:30
commit a5d76d8d5a
5 changed files with 318 additions and 0 deletions

11
Dockerfile Normal file
View File

@ -0,0 +1,11 @@
# Debian GNU/Linux 10 (1.13.10-buster)
FROM golang:1.13.10-buster
# copy entrypoint file
COPY entrypoint.go /usr/bin/entrypoint.go
# change mode of the entrypoint file
RUN chmod +x /usr/bin/entrypoint.go
# set entrypoint command
ENTRYPOINT [ "go", "run", "/usr/bin/entrypoint.go" ]

49
README.md Normal file
View File

@ -0,0 +1,49 @@
# GitHub Action: go-build-action
This actions generates cross-platform executable files from a Go module.
![release](/assets/release.png)
# Workflow setup
```yaml
# workflow name
name: Generate release-artifacts
# on events
on:
release:
types:
- created
# workflow tasks
jobs:
generate:
name: Generate cross-platform builds
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2
- name: Generate build files
uses: thatisuday/go-cross-build@v1
with:
platforms: 'linux/amd64, darwin/amd64, windows/amd64'
package: 'demo'
name: 'program'
compress: 'true'
dest: 'dist'
```
### option: **platforms**
The `platforms` option specifies comma-separated platform names to create binary-executable files for. To see the list of supported platforms, use `go tool dist list` command.
### option: **package**
If the module (_repository_) itself is a Go package, then `package` option value should be an empty string (''). If the repository contains a package directory, then `package` value should be the directory name.
### option: **compress**
The `compress` option if set to `'true'` will generate **compressed-tar** archive files for the each platform-build file. The resulting archive file also contains `README.md` and `LICENSE` file if they exist inside the root of the repository. In this mode, the binary executable file name is taken from the `name` option value.
### option: **name**
The `name` option sets a prefix for the build filenames. In compression mode, this prefix is applied to archive files and binary executable filename is set to this value.
### option: **dest**
The `dest` option sets the output directory for the build files. This should be a relative directory without leading `./`.

43
action.yml Normal file
View File

@ -0,0 +1,43 @@
# action name
name: 'go-cross-build'
# action author
author: 'Uday Hiwarale <thatisuday@gmail.com>'
# action description
description: 'Generates cross-platform executable files from a Go module.'
# action input values
inputs:
platforms:
description: 'Comma-separated list of "<kernel>/<arch>" combinations.'
default: 'linux/386,linux/amd64'
required: false
package:
description: 'Package (directory) in the module to build. By default, builds the module directory.'
default: ''
required: false
compress:
description: 'Compress each build file inside a ".tar.gz" archive.'
default: 'false'
required: false
name:
description: 'Binary executable filename and filenames prefix for the build files.'
default: 'program'
required: false
dest:
description: 'Destination directory inside workspace to output build-artifacts.'
default: 'build'
required: false
# action runner (golang:latest image)
runs:
using: 'docker'
image: 'Dockerfile'
env:
GO111MODULE: 'on'
# branding
branding:
icon: terminal
color: green

BIN
assets/release.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

215
entrypoint.go Normal file
View File

@ -0,0 +1,215 @@
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
)
/*************************************/
// check if file exists (relative to the current directory)
func fileExists(path string) bool {
// get current directory
cwd, _ := os.Getwd()
// get an absolute path of the file
absPath := filepath.Join(cwd, path)
// access file information
if _, err := os.Stat(absPath); err != nil {
return !os.IsNotExist(err) // return `false` if doesn't exist
}
// file exists
return true
}
// copy file using `cp` command
func copyFile(src, dest string) {
if err := exec.Command("cp", src, dest).Run(); err != nil {
fmt.Println("An error occurred during copy operation:", src, "=>", dest)
os.Exit(1)
}
}
/*************************************/
// build the package for a platform
func build(packageName, destDir string, platform map[string]string, compress bool) {
// platform config
platformKernel := platform["kernel"]
platformArch := platform["arch"]
// binary executable file path
inputName := os.Getenv("INPUT_NAME")
// build file name (same as the `inputName` if compression is enabled)
buildFileName := fmt.Sprintf("%s-%s-%s", inputName, platformKernel, platformArch)
if compress {
buildFileName = inputName
}
// append `.exe` file-extension for windows
if platformKernel == "windows" {
buildFileName += ".exe"
}
// workspace directory
workspaceDir := os.Getenv("GITHUB_WORKSPACE")
// destination directory path
destDirPath := filepath.Join(workspaceDir, destDir)
// join destination path
buildFilePath := filepath.Join(destDirPath, buildFileName)
// package directory local path
var packagePath string
if packageName == "" {
packagePath = "."
} else {
packagePath = "./" + packageName
}
/*------------*/
// command-line options for the `go build` command
buildOptions := []string{"build", "-buildmode", "exe", "-o", buildFilePath, packagePath}
// generate `go build` command
buildCmd := exec.Command("go", buildOptions...)
// set environment variables
buildCmd.Env = append(os.Environ(), []string{
fmt.Sprintf("GOOS=%s", platformKernel),
fmt.Sprintf("GOARCH=%s", platformArch),
}...)
// execute `go build` command
fmt.Println("Creating a build using :", buildCmd.String())
if output, err := buildCmd.Output(); err != nil {
fmt.Println("An error occurred during build:", err)
os.Exit(1)
} else {
fmt.Printf("%s\n", output)
}
/*------------------------------*/
// create a compressed `.tar.gz` file
if compress {
// compressed gzip file name
gzFileName := fmt.Sprintf("%s-%s-%s.tar.gz", inputName, platformKernel, platformArch)
/*------------*/
// file to compress (default: build file)
includeFiles := []string{buildFileName}
// copy "README.md" file inside destination directory
if fileExists("README.md") {
copyFile("README.md", filepath.Join(destDirPath, "README.md"))
includeFiles = append(includeFiles, "README.md")
}
// copy "LICENSE" file inside destination directory
if fileExists("LICENSE") {
copyFile("LICENSE", filepath.Join(destDirPath, "LICENSE"))
includeFiles = append(includeFiles, "LICENSE")
}
/*------------*/
// command-line options for the `tar` command
tarOptions := append([]string{"-cvzf", gzFileName}, includeFiles...)
// generate `tar` command
tarCmd := exec.Command("tar", tarOptions...)
// set working directory for the command
tarCmd.Dir = destDirPath
// execute `tar` command
fmt.Println("Compressing build file using:", tarCmd.String())
if err := tarCmd.Run(); err != nil {
fmt.Println("An error occurred during compression:", err)
os.Exit(1)
}
/*------------*/
// generate cleanup command
cleanCmd := exec.Command("rm", append([]string{"-f"}, includeFiles...)...)
// set working directory for the command
cleanCmd.Dir = destDirPath
// start cleanup process
fmt.Println("Performing cleanup operation using:", cleanCmd.String())
if err := cleanCmd.Run(); err != nil {
fmt.Println("An error occurred during cleaup:", err)
os.Exit(1)
}
}
}
/*************************************/
func main() {
// get input variables from action
inputPlatforms := os.Getenv("INPUT_PLATFORMS")
inputPackage := os.Getenv("INPUT_PACKAGE")
inputCompress := os.Getenv("INPUT_COMPRESS")
inputDest := os.Getenv("INPUT_DEST")
// package name to build
packageName := strings.ReplaceAll(inputPackage, " ", "")
// destination directory
destDir := strings.ReplaceAll(inputDest, " ", "")
// split platform names by comma (`,`)
platforms := strings.Split(inputPlatforms, ",")
// should compress build file
compress := false
if strings.ToLower(inputCompress) == "true" {
compress = true
}
// for each platform, execute `build` function
for _, platform := range platforms {
// split platform by `/` (and clean all whitespaces)
platformSpec := strings.Split(strings.ReplaceAll(platform, " ", ""), "/")
// create a `map` of `kernel` and `arch`
platformMap := map[string]string{
"kernel": platformSpec[0],
"arch": platformSpec[1],
}
// execute `build` function
build(packageName, destDir, platformMap, compress)
}
/*------------*/
// list files inside destination directory
if output, err := exec.Command("ls", "-alh", destDir).Output(); err != nil {
fmt.Println("An error occurred during ls operation:", err)
os.Exit(1)
} else {
fmt.Println("--- BUILD FILES ---")
fmt.Printf("%s\n", output)
}
}