Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make SCons not include the base dir in zip files?

Tags:

python

zip

scons

SCons provides a Zip builder to produce zip files from groups of files. For example, suppose we have a folder foo that looks like this:

foo/
foo/blah.txt

and we create the zip file foo.zip from a folder foo:

env.Zip('foo.zip', 'foo/')

This produces a zip file:

$ unzip -l foo.zip
Archive:  foo.zip
  foo/
  foo/foo.txt

However, suppose we are using a VariantDir of bar, which contains foo:

bar/
bar/foo/
bar/foo/foo.txt

Because we are in a VariantDir, we still use the same command to create the zip file, even though it has slightly different effects:

env.Zip('foo.zip', 'foo/')

This produces the zip file:

$ unzip -l bar/foo.zip
Archive:  bar/foo.zip
  bar/foo/
  bar/foo/foo.txt

The problem is extra bar/ prefix for each of the files within the zip. If this was not SCons, the simple solution would be to cd into bar and call zip from within there with something like cd bar; zip -r foo.zip foo/. However, this is weird/difficult with SCons, and at any rate seems very un-SCons-like. Is there a better solution?

like image 995
Xymostech Avatar asked Oct 16 '25 05:10

Xymostech


2 Answers

You can create a SCons Builder which accomplishes this task. We can use the standard Python zipfile to make the zip files. We take advantage of zipfile.write, which allows us to specify a file to add, as well as what it should be called within the zip:

zf.write('foo/bar', 'bar') # save foo/bar as bar

To get the right paths, we use os.path.relpath with the path of the base file to find the path to the overall file.

Finally, we use os.walk to walk through contents of directories that we want to add, and call the previous two functions to add them, correctly, to the final zip.

import os.path
import zipfile

def zipbetter(target, source, env):
    # Open the zip file with appending, so multiple calls will add more files
    zf = zipfile.ZipFile(str(target[0]), 'a', zipfile.ZIP_DEFLATED)
    for s in source:
        # Find the path of the base file
        basedir = os.path.dirname(str(s))
        if s.isdir():
            # If the source is a directory, walk through its files
            for dirpath, dirnames, filenames in os.walk(str(s)):
                for fname in filenames:
                    path = os.path.join(dirpath, fname)
                    if os.path.isfile(path):
                        # If this is a file, write it with its relative path
                        zf.write(path, os.path.relpath(path, basedir))
        else:
            # Otherwise, just write it to the file
            flatname = os.path.basename(str(s))
            zf.write(str(s), flatname)
    zf.close()

# Make a builder using the zipbetter function, that takes SCons files
zipbetter_bld = Builder(action = zipbetter,
                        target_factory = SCons.Node.FS.default_fs.Entry,
                        source_factory = SCons.Node.FS.default_fs.Entry)

# Add the builder to the environment
env.Append(BUILDERS = {'ZipBetter' : zipbetter_bld})

Call it just like the normal SCons Zip:

env.ZipBetter('foo.zip', 'foo/')
like image 189
Xymostech Avatar answered Oct 18 '25 17:10

Xymostech


Using construct variable ‘ZIPROOT’

like image 44
user7300330 Avatar answered Oct 18 '25 19:10

user7300330