Thanks. I committed this patch. One thing I didn't understand is the comment: "The only current limitation is support for links in OSv". I see you did handle symlinks... Is this an old comment?
-- Nadav Har'El [email protected] On Sat, Aug 19, 2017 at 7:51 PM, Waldemar Kozaczuk <[email protected]> wrote: > 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]> > > --- > modules/empty/module.py | 0 > scripts/build | 15 +++++++- > scripts/export_manifest.py | 93 ++++++++++++++++++++++++++++++ > ++++++++++++++++ > scripts/manifest_common.py | 78 ++++++++++++++++++++++++++++++++++++++ > scripts/module.py | 15 ++++++-- > scripts/upload_manifest.py | 79 +-------------------------------------- > 6 files changed, 198 insertions(+), 82 deletions(-) > create mode 100644 modules/empty/module.py > create mode 100755 scripts/export_manifest.py > create mode 100644 scripts/manifest_common.py > > diff --git a/modules/empty/module.py b/modules/empty/module.py > new file mode 100644 > index 0000000..e69de29 > diff --git a/scripts/build b/scripts/build > index 7cee19c..3d42f78 100755 > --- a/scripts/build > +++ b/scripts/build > @@ -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 > @@ -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 > diff --git a/scripts/export_manifest.py b/scripts/export_manifest.py > new file mode 100755 > index 0000000..6bade92 > --- /dev/null > +++ b/scripts/export_manifest.py > @@ -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() > diff --git a/scripts/manifest_common.py b/scripts/manifest_common.py > new file mode 100644 > index 0000000..414cce3 > --- /dev/null > +++ b/scripts/manifest_common.py > @@ -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 > diff --git a/scripts/module.py b/scripts/module.py > index ec96e89..548dd0c 100755 > --- a/scripts/module.py > +++ b/scripts/module.py > @@ -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 > @@ -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:") > @@ -260,7 +267,9 @@ if __name__ == "__main__": > 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") > diff --git a/scripts/upload_manifest.py b/scripts/upload_manifest.py > index de570f0..736cd34 100755 > --- a/scripts/upload_manifest.py > +++ b/scripts/upload_manifest.py > @@ -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 > @@ -10,64 +11,6 @@ except ImportError: > # 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)) > @@ -118,24 +61,6 @@ def upload(osv, manifest, depends): > + 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("->"): > -- > 2.7.4 > > -- > You received this message because you are subscribed to the Google Groups > "OSv Development" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to [email protected]. > For more options, visit https://groups.google.com/d/optout. > -- You received this message because you are subscribed to the Google Groups "OSv Development" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. For more options, visit https://groups.google.com/d/optout.
