Skip to content

Commit

Permalink
Enhanced scripts/build and related Python scripts to support export f…
Browse files Browse the repository at this point in the history
…iles that make up an image versus uploading them to an image

This patch enhances OSv build scripts to allow exporting files in addition
of uploading them to an image. It addresses 3 usage scenarios described in
 #900 and demostrated by examples below:

- ./scripts/build image=empty export=all - export files that are specified is usr.manifest.skel in order to be able to produce capstan osv.bootstrap package

- ./scripts/build image=java-isolated export=all usrskel=none - export files that are part of OSv modules (./modules directory) along with all other dependent modules minus what is part of usr.manifest.skel

- ./scripts/build image=openjdk8-zulu-compact1 export=selected usrskel=none - export files that are part of an app (most applicable to apps/openjdk8-****) without any dependent modules

The export logic is enabled by passing new parameter export [=none|all|selected] to scripts/build.
Exported files are placed under build/export directory or directory indocated by export_dir parameter.

Please note that the changes are backwards compatible. However changes to scripts/upload_manifest.py
 will break patches that are part of https://github.com/mikelangelo-project/capstan-packages/tree/master/docker_files/common. Hopefully this patch will make some of these patches obsolete.

Fixed #900

Signed-off-by: Waldemar Kozaczuk <[email protected]>

Message-Id: <[email protected]>
  • Loading branch information
wkozaczuk authored and nyh committed Aug 20, 2017
1 parent 77b1f05 commit 279effd
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 82 deletions.
Empty file added modules/empty/module.py
Empty file.
15 changes: 13 additions & 2 deletions scripts/build
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,12 @@ then
usrskel_arg="--usrskel ${vars[usrskel]}"
fi

jdkbase=$jdkbase ARCH=$arch mode=$mode OSV_BASE=$SRC OSV_BUILD_PATH=$OSV_BUILD_PATH scripts/module.py $j_arg build -c $modules $usrskel_arg
export=${vars[export]-none}
if [ "$export" == "selected" ]
then
no_required_arg="--no-required"
fi
jdkbase=$jdkbase ARCH=$arch mode=$mode OSV_BASE=$SRC OSV_BUILD_PATH=$OSV_BUILD_PATH scripts/module.py $j_arg build -c $modules $usrskel_arg $no_required_arg

bootfs_manifest=$manifest make "${args[@]}" | tee -a build.out
# check exit status of make
Expand Down Expand Up @@ -180,7 +185,13 @@ zfs)
qemu-img convert -f raw -O qcow2 bare.raw usr.img
qemu-img resize usr.img ${fs_size}b >/dev/null 2>&1

$SRC/scripts/upload_manifest.py -o usr.img -m usr.manifest -D jdkbase=$jdkbase -D gccbase=$gccbase -D glibcbase=$glibcbase -D miscbase=$miscbase
if [ "$export" == "none" ]
then
$SRC/scripts/upload_manifest.py -o usr.img -m usr.manifest -D jdkbase=$jdkbase -D gccbase=$gccbase -D glibcbase=$glibcbase -D miscbase=$miscbase
else
export_dir=${vars[export_dir]-$SRC/build/export}
$SRC/scripts/export_manifest.py -e $export_dir -m usr.manifest -D jdkbase=$jdkbase -D gccbase=$gccbase -D glibcbase=$glibcbase -D miscbase=$miscbase
fi
;;
ramfs)
qemu-img convert -f raw -O qcow2 loader.img usr.img
Expand Down
93 changes: 93 additions & 0 deletions scripts/export_manifest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/usr/bin/python

import optparse, os, shutil
from manifest_common import add_var, expand, unsymlink, read_manifest, defines, strip_file

# This will export the package based on the provided manifest file. It uses the same mechanism to
# get the files that need copying as the actual upload process. The only current limitation is
# support for links in OSv, e.g., /etc/mnttab: ->/proc/mounts.
def export_package(manifest, dest):
abs_dest = os.path.abspath(dest)
print "[INFO] exporting into directory %s" % abs_dest

# Remove and create the base directory where we are going to put all package files.
if os.path.exists(abs_dest):
shutil.rmtree(abs_dest)
os.makedirs(abs_dest)

files = list(expand(manifest))
files = [(x, unsymlink(y % defines)) for (x, y) in files]

for name, hostname in files:
name = name[1:] if name.startswith("/") else name
name = os.path.join(abs_dest, name)

if hostname.startswith("->"):
link_source = hostname[2:]
target_dir = os.path.dirname(name)

if link_source.startswith("/"):
link_source = os.path.join(abs_dest, link_source[1:])
else:
link_source = os.path.abspath(os.path.join(target_dir, link_source))

link_source = os.path.relpath(link_source, target_dir)

if not os.path.exists(target_dir):
os.makedirs(target_dir)

os.symlink(link_source, name)
print "[INFO] added link %s -> %s" % (name, link_source)

else:
# If it is a file, copy it to the target directory.
if os.path.isfile(hostname):
# Make sure the target dir exists
dirname = os.path.dirname(name)
if not os.path.exists(dirname):
os.makedirs(dirname)

if hostname.endswith("-stripped.so"):
continue
hostname = strip_file(hostname)

shutil.copy(hostname, name)
print "[INFO] exported %s" % name
elif os.path.isdir(hostname):
# If hostname is a dir, it is only a request to create the folder on guest. Nothing to copy.
if not os.path.exists(name):
os.makedirs(name)
print "[INFO] created dir %s" % name

else:
# Inform the user that the rule cannot be applied. For example, this happens for links in OSv.
print "[ERR] unable to export %s" % hostname


def main():
make_option = optparse.make_option

opt = optparse.OptionParser(option_list=[
make_option('-m',
dest='manifest',
help='read manifest from FILE',
metavar='FILE'),
make_option('-D',
type='string',
help='define VAR=DATA',
metavar='VAR=DATA',
action='callback',
callback=add_var),
make_option('-e',
dest='export',
help='exports the contents of the usr.manifest into a given folder',
metavar='FILE'),
])

(options, args) = opt.parse_args()

manifest = read_manifest(options.manifest)
export_package(manifest, options.export)

if __name__ == "__main__":
main()
78 changes: 78 additions & 0 deletions scripts/manifest_common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/python

import os, io, re, subprocess

defines = {}

def add_var(option, opt, value, parser):
var, val = value.split('=')
defines[var] = val

def expand(items):
for name, hostname in items:
if name.endswith('/**') and hostname.endswith('/**'):
name = name[:-2]
hostname = hostname[:-2]
for dirpath, dirnames, filenames in os.walk(hostname):
for filename in filenames:
relpath = dirpath[len(hostname):]
if relpath != "":
relpath += "/"
yield (name + relpath + filename,
hostname + relpath + filename)
elif '/&/' in name and hostname.endswith('/&'):
prefix, suffix = name.split('/&/', 1)
yield (prefix + '/' + suffix, hostname[:-1] + suffix)
else:
yield (name, hostname)

def unsymlink(f):
if f.startswith('!'):
return f[1:]
if f.startswith('->'):
return f
try:
link = os.readlink(f)
if link.startswith('/'):
# try to find a match
base = os.path.dirname(f)
while not os.path.exists(base + link):
if base == '/':
return f
base = os.path.dirname(base)
else:
base = os.path.dirname(f) + '/'
return unsymlink(base + link)
except Exception:
return f

# Reads the manifest and returns it as a list of pairs (guestpath, hostpath).
def read_manifest(fn):
ret = []
comment = re.compile("^[ \t]*(|#.*|\[manifest])$")
with open(fn, 'r') as f:
for line in f:
line = line.rstrip();
if comment.match(line): continue
components = line.split(": ", 2)
guestpath = components[0].strip();
hostpath = components[1].strip()
ret.append((guestpath, hostpath))
return ret

def strip_file(filename):
def to_strip(filename):
ff = os.path.abspath(filename)
osvdir = os.path.abspath('../..')
return ff.startswith(os.getcwd()) or \
ff.startswith(osvdir + "/modules") or \
ff.startswith(osvdir + "/apps")

stripped_filename = filename
if filename.endswith(".so") and to_strip(filename):
stripped_filename = filename[:-3] + "-stripped.so"
if not os.path.exists(stripped_filename) \
or (os.path.getmtime(stripped_filename) < \
os.path.getmtime(filename)):
subprocess.call(["strip", "-o", stripped_filename, filename])
return stripped_filename
15 changes: 12 additions & 3 deletions scripts/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ def build(args):
print("Using image config: %s" % image_config_file)
config = resolve.local_import(image_config_file)
run_list = config.get('run', [])
selected_modules = []
else:
# If images/image_config doesn't exist, assume image_config is a
# comma-separated list of module names, and build an image from those
Expand Down Expand Up @@ -203,10 +204,16 @@ def build(args):
else:
api.require_running(name)

# Add moduless thare are implictly required if others are present
# Add modules that are implicitly required if others are present
resolve.resolve_required_modules_if_other_is_present()

modules = resolve.get_required_modules()
# By default append manifests from all modules resolved through api.require()
# otherwise (add_required_to_manifest=False) only append manifests from the selected_modules
if args.add_required_to_manifest:
modules = resolve.get_required_modules()
else:
modules = list(module for module in resolve.get_required_modules() if module.name in selected_modules)

modules_to_run = resolve.get_modules_to_run()

print("Modules:")
Expand Down Expand Up @@ -260,7 +267,9 @@ def clean(args):
help="image configuration name. Looked up in " + image_configs_dir)
build_cmd.add_argument("--usrskel", action="store", default="default",
help="override default usr.manifest.skel")
build_cmd.set_defaults(func=build)
build_cmd.add_argument("--no-required", dest="add_required_to_manifest", action="store_false",
help="do not add files to usr.manifest from modules implicitly resolved through api.require()")
build_cmd.set_defaults(func=build,add_required_to_manifest=True)

clean_cmd = subparsers.add_parser("clean", help="Clean modules")
clean_cmd.add_argument("-q", "--quiet", action="store_true")
Expand Down
79 changes: 2 additions & 77 deletions scripts/upload_manifest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/python

import os, optparse, io, subprocess, socket, threading, stat, sys, re
import optparse, os, subprocess, socket, threading, stat, sys
from manifest_common import add_var, expand, unsymlink, read_manifest, defines, strip_file

try:
import StringIO
Expand All @@ -10,64 +11,6 @@
# This works on Python 3
StringIO = io.StringIO

defines = {}

def add_var(option, opt, value, parser):
var, val = value.split('=')
defines[var] = val

def expand(items):
for name, hostname in items:
if name.endswith('/**') and hostname.endswith('/**'):
name = name[:-2]
hostname = hostname[:-2]
for dirpath, dirnames, filenames in os.walk(hostname):
for filename in filenames:
relpath = dirpath[len(hostname):]
if relpath != "":
relpath += "/"
yield (name + relpath + filename,
hostname + relpath + filename)
elif '/&/' in name and hostname.endswith('/&'):
prefix, suffix = name.split('/&/', 1)
yield (prefix + '/' + suffix, hostname[:-1] + suffix)
else:
yield (name, hostname)

def unsymlink(f):
if f.startswith('!'):
return f[1:]
if f.startswith('->'):
return f
try:
link = os.readlink(f)
if link.startswith('/'):
# try to find a match
base = os.path.dirname(f)
while not os.path.exists(base + link):
if base == '/':
return f
base = os.path.dirname(base)
else:
base = os.path.dirname(f) + '/'
return unsymlink(base + link)
except Exception:
return f

# Reads the manifest and returns it as a list of pairs (guestpath, hostpath).
def read_manifest(fn):
ret = []
comment = re.compile("^[ \t]*(|#.*|\[manifest])$")
with open(fn, 'r') as f:
for line in f:
line = line.rstrip();
if comment.match(line): continue
components = line.split(": ", 2)
guestpath = components[0].strip();
hostpath = components[1].strip()
ret.append((guestpath, hostpath))
return ret

def upload(osv, manifest, depends):
manifest = [(x, y % defines) for (x, y) in manifest]
files = list(expand(manifest))
Expand Down Expand Up @@ -118,24 +61,6 @@ def cpio_header(filename, mode, filesize):
+ cpio_field(0, 8) # check
+ filename + b'\0')

def to_strip(filename):
ff = os.path.abspath(filename);
osvdir = os.path.abspath('../..');
return ff.startswith(os.getcwd()) or \
ff.startswith(osvdir + "/modules") or \
ff.startswith(osvdir + "/apps")

def strip_file(filename):
stripped_filename = filename
if filename.endswith(".so") and to_strip(filename):
stripped_filename = filename[:-3] + "-stripped.so"
if not os.path.exists(stripped_filename) \
or (os.path.getmtime(stripped_filename) < \
os.path.getmtime(filename)):
subprocess.call(["strip", "-o", stripped_filename, filename])
return stripped_filename


# Send the files to the guest
for name, hostname in files:
if hostname.startswith("->"):
Expand Down

0 comments on commit 279effd

Please sign in to comment.