Skip to content

Commit

Permalink
Merge pull request #277 from firesim/cached-br
Browse files Browse the repository at this point in the history
Cached Buildroot Images
  • Loading branch information
abejgonzalez committed Jun 8, 2023
2 parents 9fd62c8 + 28e2151 commit f553a22
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 18 deletions.
35 changes: 29 additions & 6 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
name: run-tests

on:
# run ci when the following branches are pushed to (i.e. after merge)
push:
branches:
- master
# run ci when pring to following branches (note: ci runs on the merge commit of the pr!)
pull_request:
branches:
- master

defaults:
run:
shell: bash -leo pipefail {0}

env:
REMOTE_WORK_DIR: /scratch/buildbot/firemarshal-ci-shared/firemarshal-${{ github.sha }}
PERSONAL_ACCESS_TOKEN: ${{ secrets.FIREMARSHAL_BR_UPLOAD_GH_PERSONAL_ACCESS_KEY }}

jobs:
cancel-prior-workflows:
Expand Down Expand Up @@ -52,7 +61,7 @@ jobs:
name: setup-repo
needs: change-filters
if: needs.change-filters.outputs.run-core == 'true'
runs-on: self-hosted
runs-on: firemarshal
steps:
- name: Delete old checkout
run: |
Expand All @@ -61,7 +70,7 @@ jobs:
rm -rf ${{ env.REMOTE_WORK_DIR }}/.* || true
rm -rf ${{ github.workspace }}/* || true
rm -rf ${{ github.workspace }}/.* || true
- uses: actions/checkout@v3
- name: Setup repo copy
run: |
Expand Down Expand Up @@ -100,10 +109,24 @@ jobs:
fi
ulimit -Sn $(ulimit -Hn)
build-upload-br-image:
name: build-upload-br-image
needs: [setup-repo]
if: ${{ github.ref == 'master' }}
runs-on: firemarshal
steps:
- name: Build buildroot image
run: |
cd ${{ env.REMOTE_WORK_DIR }}
eval "$(conda shell.bash hook)"
conda activate $PWD/.conda-env
./marshal -v build br-base.json
./scripts/upload-br-image.py
run-tests:
name: run-tests
needs: [setup-repo]
runs-on: self-hosted
runs-on: firemarshal
steps:
- name: Run tests (- spike tests)
run: |
Expand All @@ -119,9 +142,9 @@ jobs:
cleanup:
name: cleanup
needs: [setup-repo, run-tests]
runs-on: self-hosted
if: ${{ always() && contains(join(needs.*.result, ','), 'success') }}
needs: [setup-repo, build-upload-br-image, run-tests]
runs-on: firemarshal
if: ${{ always() }}
steps:
- name: Delete repo copy and conda env
run: |
Expand Down
77 changes: 77 additions & 0 deletions .github/workflows/weekly-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: weekly-build

on:
schedule:
# run at 00:00 on sunday
- cron: "0 0 * * 0"

defaults:
run:
shell: bash -leo pipefail {0}

env:
REMOTE_WORK_DIR: /scratch/buildbot/firemarshal-ci-shared/firemarshal-${{ github.sha }}
PERSONAL_ACCESS_TOKEN: ${{ secrets.FIREMARSHAL_BR_UPLOAD_GH_PERSONAL_ACCESS_KEY }}

jobs:
setup-repo:
name: setup-repo
needs: change-filters
if: needs.change-filters.outputs.run-core == 'true'
runs-on: firemarshal
steps:
- name: Delete old checkout
run: |
ls -alh .
rm -rf ${{ env.REMOTE_WORK_DIR }}/* || true
rm -rf ${{ env.REMOTE_WORK_DIR }}/.* || true
rm -rf ${{ github.workspace }}/* || true
rm -rf ${{ github.workspace }}/.* || true
- uses: actions/checkout@v3
- name: Setup repo copy
run: |
git clone $GITHUB_WORKSPACE ${{ env.REMOTE_WORK_DIR }}
- name: Setup conda (install all deps)
run: |
cd ${{ env.REMOTE_WORK_DIR }}
conda env create -f ./conda-reqs.yaml -p ./.conda-env
eval "$(conda shell.bash hook)"
conda install -y -p $PWD/.conda-env -c ucb-bar riscv-tools=1.0.4
- name: Initialize all submodules
run: |
cd ${{ env.REMOTE_WORK_DIR }}
eval "$(conda shell.bash hook)"
conda activate $PWD/.conda-env
./init-submodules.sh
- name: Verify open file limits
run: |
HARD_LIMIT=$(ulimit -Hn)
REQUIRED_LIMIT=16384
if [ "$HARD_LIMIT" -lt "$REQUIRED_LIMIT" ]; then
echo "ERROR: Your system does not support an open files limit (the output of 'ulimit -Sn' and 'ulimit -Hn') of at least $REQUIRED_LIMIT, which is required to workaround a bug in buildroot. You will not be able to build a Linux distro with FireMarshal until this is addressed."
exit 1
fi
ulimit -Sn $(ulimit -Hn)
build-upload-br-image:
name: build-upload-br-image
needs: [setup-repo]
runs-on: firemarshal
steps:
- name: Build buildroot image
run: |
cd ${{ env.REMOTE_WORK_DIR }}
eval "$(conda shell.bash hook)"
conda activate $PWD/.conda-env
./marshal -v build br-base.json
./scripts/upload-br-image.py
cleanup:
name: cleanup
needs: [setup-repo, build-upload-br-image]
runs-on: firemarshal
if: ${{ always() }}
steps:
- name: Delete repo copy and conda env
run: |
rm -rf ${{ env.REMOTE_WORK_DIR }}
2 changes: 1 addition & 1 deletion boards/default/distros/bare/bare.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def getWorkload(self):
'builder': self
}

def buildBaseImage(self):
def buildBaseImage(self, task, changed):
raise NotImplementedError("Baremetal workloads currently do not support disk images")

def upToDate(self):
Expand Down
68 changes: 65 additions & 3 deletions boards/default/distros/br/br.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,35 @@
import hashlib
import wlutil
import re
import zipfile
import urllib.request
import time
import logging

# Note: All argument paths are expected to be absolute paths

# Some common directories for this module (all absolute paths)
br_dir = pathlib.Path(__file__).parent
fm_dir = br_dir.parents[3]
overlay = br_dir / 'overlay'

# Buildroot puts its output images here
img_dir = br_dir / 'buildroot' / 'output' / 'images'

GH_REPO = 'firemarshal-public-br-images'
GH_ORG = 'firesim'
URL_PREFIX = f"https://raw.githubusercontent.com/{GH_ORG}/{GH_REPO}"


def get_url(file_path):
return f"{URL_PREFIX}/main/{file_path}"


def make_relative(path):
path_str = str(path)
return path_str.replace(str(fm_dir) + "/", "")


initTemplate = string.Template("""#!/bin/sh
SYSLOGD_ARGS=-n
Expand Down Expand Up @@ -44,7 +63,7 @@


def hashOpts(opts):
"""Return a unique description of this builder, based on the provided opts"""
"""Return a unique description of this builder, based on the provided opts and sha of buildroot"""

if len(opts) == 0:
return None
Expand All @@ -56,7 +75,13 @@ def hashOpts(opts):
h.update(cf.read())

if 'environment' in opts:
h.update(str(opts['environment']).encode('utf-8'))
# use the relative path for the hash (so it is reproducible across machines)
env_str = make_relative(str(opts['environment']))
h.update(env_str.encode('utf-8'))

gs = wlutil.checkGitStatus(br_dir / 'buildroot')
# if the repo is dirty this hash will always be different from the prior run
h.update(str(gs).encode('utf-8'))

return h.hexdigest()[0:4]

Expand Down Expand Up @@ -164,11 +189,48 @@ def configure(self, env):
wlutil.run([mergeScript] + kFrags, cwd=(br_dir / 'buildroot'), env=env)

# Build a base image in the requested format and return an absolute path to that image
def buildBaseImage(self):
def buildBaseImage(self, task, changed):
"""Ensures that the image file specified by baseConfig() exists and is up to date.
This is called as a doit task.
See more information about the arguments here (or in the source code): https://pydoit.org/tasks.html#keywords-with-task-metadata
Args:
task: a Task object instance (all metadata about the "task" being run) - see doit's task.py source for the variable list
changed: list of file depencies that have changed since the last successful execution
"""
log = logging.getLogger()

# optimization to get cached br distro image
img_rel_path = make_relative(str(self.outputImg))
cached_url = get_url(img_rel_path + ".zip")
cached_local = f"{br_dir}/{self.outputImg.name}.zip"

# try N times then move on
for i in range(3):
try:
log.info(f"Attempting to download cached image: {cached_url}")
urllib.request.urlretrieve(cached_url, cached_local)
break
except Exception as e:
log.debug(f"urlretrieve exception: {e}")
time.sleep(3)

if os.path.exists(cached_local):
assert len(task.targets) == 1, "Multiple targets detected for buildroot"
target_doesnt_exist = not os.path.exists(task.targets[0])
file_deps_not_changed = not changed
log.debug(f"Target doesn't exist: {target_doesnt_exist}, File dep(s) not changed: {file_deps_not_changed} {changed}")
if target_doesnt_exist and file_deps_not_changed:
log.info(f"Unzipping cached image: {cached_local}")
with zipfile.ZipFile(cached_local, 'r') as zip_ref:
self.outputImg.parent.mkdir(parents=True, exist_ok=True)
zip_ref.extractall(self.outputImg.parent)
os.remove(cached_local)
log.info(f"Skipping full buildroot build. Using cached image {self.outputImg} from {cached_local}")
return
os.remove(cached_local)

try:
wlutil.checkSubmodule(br_dir / 'buildroot')

Expand Down
2 changes: 1 addition & 1 deletion boards/default/distros/fedora/fedora.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def getWorkload(self):
'img': fed_dir / "rootfs.img"
}

def buildBaseImage(self):
def buildBaseImage(self, task, changed):
wlutil.run(['make', "rootfs.img"], cwd=fed_dir)

def fileDeps(self):
Expand Down
5 changes: 3 additions & 2 deletions conda-reqs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ platforms:

dependencies:
- qemu # from ucb-bar channel - https://github.com/ucb-bar/qemu-feedstock
- python>=3.8
- python>=3.9
- rsync
- psutil
- doit>=0.34
Expand All @@ -31,7 +31,7 @@ dependencies:
- gxx
- conda-gcc-specs
- binutils
- sysroot_linux-64>=2.17
- sysroot_linux-64=2.17
- patch
- which
- sed
Expand All @@ -46,3 +46,4 @@ dependencies:
- wget
- findutils
- lzop
- pygithub
85 changes: 85 additions & 0 deletions scripts/upload-br-image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env python3

from pathlib import Path
import os
from github import Github
import time
import zipfile

script_dir = Path(os.path.dirname(os.path.realpath(__file__)))
fm_dir = script_dir.parent

GH_REPO = 'firemarshal-public-br-images'
GH_ORG = 'firesim'
URL_PREFIX = f"https://raw.githubusercontent.com/{GH_ORG}/{GH_REPO}"


# taken from https://stackoverflow.com/questions/63427607/python-upload-files-directly-to-github-using-pygithub
# IMPORTANT: only works for binary files! (i.e. tar.gz or zip files)
def upload_binary_file(local_file_path, gh_file_path):
print(f":DEBUG: Attempting to upload {local_file_path} to {gh_file_path}")

g = Github(os.environ['PERSONAL_ACCESS_TOKEN'])

repo = g.get_repo(f'{GH_ORG}/{GH_REPO}')
all_files = []
contents = repo.get_contents("")
while contents:
file_content = contents.pop(0)
if file_content.type == "dir":
contents.extend(repo.get_contents(file_content.path))
else:
file = file_content
all_files.append(str(file).replace('ContentFile(path="', '').replace('")', ''))

with open(local_file_path, 'rb') as file:
content = file.read()

tries = 10
delay = 15
msg = f"Committing files from {os.environ['GITHUB_SHA']}"
upload_branch = 'main'
r = None

# Upload to github
git_file = gh_file_path
if git_file in all_files:
contents = repo.get_contents(git_file)
for n in range(tries):
try:
r = repo.update_file(contents.path, msg, content, contents.sha, branch=upload_branch)
break
except Exception as e:
print(f"Got exception: {e}")
time.sleep(delay)
assert r is not None, f"Unable to poll 'update_file' API {tries} times"
print(f"Updated: {git_file}")
else:
for n in range(tries):
try:
r = repo.create_file(git_file, msg, content, branch=upload_branch)
break
except Exception as e:
print(f"Got exception: {e}")
time.sleep(delay)
assert r is not None, f"Unable to poll 'create_file' API {tries} times"
print(f"Created: {git_file}")

return r['commit'].sha


def make_relative(path):
path_str = str(path)
return path_str.replace(f"{fm_dir}/", "")


# only caches firechip board br images
for e in (fm_dir / 'images' / 'firechip').iterdir():
if not e.is_file():
for ie in e.iterdir():
if ie.is_file() and ie.suffix == '.img' and "br." in ie.name:
ie_zip = str(ie) + ".zip"
with zipfile.ZipFile(ie_zip, 'w', zipfile.ZIP_BZIP2, compresslevel=9) as zip_ref:
zip_ref.write(ie, ie.name)
upload_binary_file(ie_zip, make_relative(ie_zip))
os.remove(ie_zip)

0 comments on commit f553a22

Please sign in to comment.