Skip to content

Commit

Permalink
make distribution ready
Browse files Browse the repository at this point in the history
  • Loading branch information
shravanasati committed Mar 30, 2024
1 parent fdbe807 commit 51c0dc0
Show file tree
Hide file tree
Showing 19 changed files with 409 additions and 119 deletions.
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Binaries for programs and plugins
bin/
dist/
*.exe
*.exe~
*.dll
Expand All @@ -19,5 +20,5 @@ bin/
atomic-summary*
atomic

*.gif
test*
test*
*.png
42 changes: 21 additions & 21 deletions LICENSE → LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
MIT License
Copyright (c) 2021-Present Shravan Asati
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
MIT License

Copyright (c) 2021-Present Shravan Asati

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![Continuous integration](https://github.com/shravanasati/atomic/actions/workflows/integrate.yml/badge.svg)](https://github.com/shravanasati/atomic/actions/workflows/integrate.yml)

![bench_demo](assets/demo.png)
![atomic demo](assets/demo.gif)


*atomic* is a simple CLI tool for making benchmarking easy.
Expand All @@ -12,12 +12,14 @@

## ✨ Features

- Benchmarks programs easily with just one command, no extra code needed
- Export the results in markdown, json and text formats
- Universal support, you can benchmark any shell command
- Choose the number of runs to perform
- Detailed benchmark summary at the end
- Fast and reliable
- Export the results in markdown, json, csv format
- Statistical Outlier Detection
- Plot the benchmarking data, comparing the different commands
- Arbitrary command support
- Constant feedback about the benchmark progress and current estimates.
- Warmup runs can be executed before the actual benchmark.
- Cache-clearing commands can be set up before each timing run.

<br>

Expand Down
Binary file added assets/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed assets/demo.png
Binary file not shown.
Binary file removed barchart.png
Binary file not shown.
Binary file removed hist.png
Binary file not shown.
1 change: 0 additions & 1 deletion internal/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ func VerifyExportFormats(formats string) ([]string, error) {
return formatList, nil
}


func Export(formats []string, filename string, results []*SpeedResult, timeUnit time.Duration) {
for _, format := range formats {
switch format {
Expand Down
2 changes: 1 addition & 1 deletion internal/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const (
RED = "\033[31m"
GREEN = "\033[32m"
YELLOW = "\033[33m"
BLUE = "\033[34m"
BLUE = "\033[34m"
PURPLE = "\033[35m"
CYAN = "\033[36m"
RESET = "\033[0m"
Expand Down
2 changes: 1 addition & 1 deletion internal/plotter.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func barPlot(results []*SpeedResult, timeUnit string) {
p.Add(bars)

p.NominalX(MapFunc[[]*SpeedResult, []string](func(r *SpeedResult) string { return r.Command }, results)...)

barWidth := max(3, len(results))
if err := p.Save(font.Length(barWidth)*vg.Inch, 3*vg.Inch, "barchart.png"); err != nil {
panic(err)
Expand Down
2 changes: 0 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,6 @@ func RunCommand(runOpts *RunOptions) *RunResult {
return runResult
}

// todo add graphing support

var MinRuns = 10
var MaxRuns = math.MaxInt64
var MinDuration = (3 * time.Second).Microseconds()
Expand Down
200 changes: 164 additions & 36 deletions scripts/build.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,179 @@
import subprocess
import hashlib
from subprocess import run, CalledProcessError
from multiprocessing import Pool, cpu_count
import shlex
import shutil
import os
from multiprocessing import Process
from typing import List
import json
from pathlib import Path

def build(appname:str, platform: str) -> None:
try:
goos = platform.split("/")[0]
goarch = platform.split("/")[1]
# build config, would be altered by init_config()
APP_NAME = "atomic"
STRIP = True # whether to strip binaries
VERBOSE = False # go compiler verbose output
FORMAT = True # format code before building binaries
PLATFORMS: list[str] = [] # list of platforms to build for
PANDOC_CONVERSION = True # whether to convert readme.md to plain text using pandoc in distributions

print(f"==> 🚧 Building executable for `{platform}`...")
os.environ["GOOS"] = goos
os.environ["GOARCH"] = goarch

outpath = f"./bin/{appname}-{goos}-{goarch}"
def hash_file(filename: str):
h = hashlib.sha256()

if goos == "windows":
outpath += ".exe"
with open(filename, "rb") as file:
chunk = 0
while chunk != b"":
chunk = file.read(1024)
h.update(chunk)

subprocess.check_output(["go", "build", "-v", "-o", outpath])
return h.hexdigest()

print(f"==> ✅ Built executable for `{platform}` at `{outpath}`.")

except Exception as e:
print(e)
print("==> ❌ An error occured! Aborting script execution.")
os._exit(1)
def init_config():
try:
global APP_NAME, STRIP, VERBOSE, FORMAT, PLATFORMS, PANDOC_CONVERSION
release_config_file = Path(__file__).parent.resolve() / 'release.config.json'
with open(str(release_config_file)) as f:
config = json.load(f)

if __name__ == "__main__":
# add all platforms to the tuple you want to build
platforms = {"windows/amd64", "linux/amd64", "darwin/amd64"}
appname = "atomic" # name of the executable
multithreaded = True # set to True to enable multithreading
APP_NAME = config["app_name"]
STRIP = config["strip_binaries"]
VERBOSE = config["verbose"]
FORMAT = config["format_code"]
PLATFORMS = config["platforms"]
PANDOC_CONVERSION = config["pandoc_conversion"]

except Exception as e:
print(f"==> ❌ Some error occured while reading the release config:\n{e}")
exit(1)


def init_folders() -> None:
"""
Makes sure that the `temp` and `dist` folders exist.
"""
if not os.path.exists("./dist"):
os.mkdir("./dist")

if not os.path.exists("./temp"):
os.mkdir("./temp")


def pack(dir: str, platform: str) -> None:
"""
Copies README, LICENSE, CHANGELOG and atomic logo to the output directory and creates an archive for the given platform.
"""
project_base = Path(__file__).parent.parent
readme_file = project_base / "temp" / "README.txt"
if not readme_file.exists():
# if pandoc conversion failed or not enabled
readme_file = project_base / "README.md"

license_file = project_base / "LICENSE.txt"
icon_file = project_base / "assets" / "icon.png"
# changelog_file = project_base / "CHANGELOG.md"

shutil.copyfile(str(readme_file), f"{dir}/README.txt")
shutil.copyfile(str(license_file), f"{dir}/LICENSE.txt")
# shutil.copyfile(str(changelog_file), f"{dir}/CHANGELOG.md")
# shutil.copyfile(str(icon_file), f"{dir}/icon.png")

splitted = platform.split("/")
build_os = splitted[0]
build_arch = splitted[1]

compression = "zip" if build_os == "windows" else "gztar"

shutil.make_archive(f"dist/{APP_NAME}_{build_os}_{build_arch}", compression, dir)


def build(platform: str) -> None:
"""
Calls the go compiler to build the application for the given platform, and the pack function.
"""
try:
print(f"==> 🚧 Building for {platform}.")
splitted = platform.split("/")
build_os = splitted[0]
build_arch = splitted[1]

output_dir = f"temp/{APP_NAME}_{build_os}_{build_arch}"
if not os.path.exists(output_dir):
os.makedirs(output_dir, exist_ok=True)

if multithreaded:
threads: List[Process] = []
executable_path = f"{output_dir}/{APP_NAME}"
if build_os == "windows":
executable_path += ".exe"

os.environ["GOOS"] = build_os
os.environ["GOARCH"] = build_arch

run(
shlex.split(
"go build -o {} {} {}".format(
executable_path,
'-ldflags="-s -w"' if STRIP else "",
"-v" if VERBOSE else "",
)
),
check=True,
)

print(f"==> ✅ Packing for {platform}.")
pack(output_dir, platform)

except CalledProcessError:
print(f"==> ❌ Failed to build for {platform}: The Go compiler returned an error.")

except Exception as e:
print(f"==> ❌ Failed to build for {platform}.")
print(e)


def generate_checksums() -> None:
project_base = Path(__file__).parent.parent
dist_folder = project_base / "dist"
checksum = ""

for item in dist_folder.iterdir():
checksum += f"{hash_file(str(item.absolute()))} {item.name}\n"

checksum_file = dist_folder / "checksums.txt"
with open(str(checksum_file), 'w') as f:
f.write(checksum)


def cleanup() -> None:
"""
Removes the `temp` folder.
"""
print("==> 👍 Cleaning up.")
shutil.rmtree("./temp")


if __name__ == "__main__":
print("==> ⌛ Initialising folders, executing prebuild commands.")
init_config()
init_folders()
if FORMAT:
run(shlex.split("go fmt ./..."), check=True)

for p in platforms:
threads.append(Process(target=build, args=(appname, p)))
try:
if PANDOC_CONVERSION:
readme_file = Path(__file__).parent.parent / "README.md"
run(
f"pandoc -s {str(readme_file)} -o ./temp/README.txt --to plain",
check=True
)
except CalledProcessError:
print("==> ⚠ Unable to convert README.md to README.txt using pandoc in distributions, make sure you've pandoc installed on your system.")

for t in threads:
t.start()
max_procs = cpu_count()
print(f"==> 🔥 Starting builds with {max_procs} parallel processes.")

for t in threads:
t.join()
with Pool(processes=max_procs) as pool:
pool.map(build, PLATFORMS)

else:
for p in platforms:
build(appname, p)
print("==> #️⃣ Generating checksums.")
generate_checksums()

print(f"==> 👍 Executables for {len(platforms)} platforms built successfully!")
cleanup()
2 changes: 1 addition & 1 deletion scripts/demo.tape
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,4 @@ Set Height 1200

Type "atomic 'scc' 'tokei'" Sleep 500ms Enter

Sleep 8s
Sleep 15s
Loading

0 comments on commit 51c0dc0

Please sign in to comment.