def test_strip_defaults(self): fake_logger = fixtures.FakeLogger(level=logging.ERROR) self.useFixture(fake_logger) parts = self.make_snapcraft_yaml() strip.main() self.assertTrue(os.path.exists(common.get_snapdir()), 'Expected a snap directory') self.assertTrue( os.path.exists( os.path.join(common.get_snapdir(), 'meta', 'package.yaml')), 'Expected a package.yaml') self.assertTrue(os.path.exists(common.get_stagedir()), 'Expected a stage directory') self.assertTrue(os.path.exists(common.get_partsdir()), 'Expected a parts directory') self.assertTrue(os.path.exists(parts[0]['part_dir']), 'Expected a part directory for the build0 part') self.assertTrue(os.path.exists(parts[0]['state_file']), 'Expected a state file for the build0 part') with open(parts[0]['state_file']) as sf: state = sf.readlines() self.assertEqual( len(state), 1, 'Expected only one line in the state ' 'file for the build0 part') self.assertEqual(state[0], 'strip', "Expected the state file for " "build0 to be 'strip'")
def _cleanup_common_directories(config): _remove_directory_if_empty(common.get_partsdir()) _remove_directory_if_empty(common.get_stagedir()) _remove_directory_if_empty(common.get_snapdir()) max_index = -1 for part in config.all_parts: step = part.last_step() if step: index = common.COMMAND_ORDER.index(step) if index > max_index: max_index = index # If no parts have been pulled, remove the parts directory. In most cases # this directory should have already been cleaned, but this handles the # case of a failed pull. should_remove_partsdir = max_index < common.COMMAND_ORDER.index('pull') if should_remove_partsdir and os.path.exists(common.get_partsdir()): logger.info('Cleaning up parts directory') shutil.rmtree(common.get_partsdir()) # If no parts have been staged, remove staging area. should_remove_stagedir = max_index < common.COMMAND_ORDER.index('stage') if should_remove_stagedir and os.path.exists(common.get_stagedir()): logger.info('Cleaning up staging area') shutil.rmtree(common.get_stagedir()) # If no parts have been stripped, remove snapping area. should_remove_snapdir = max_index < common.COMMAND_ORDER.index('strip') if should_remove_snapdir and os.path.exists(common.get_snapdir()): logger.info('Cleaning up snapping area') shutil.rmtree(common.get_snapdir())
def main(argv=None): argv = argv if argv else [] args = docopt(__doc__, argv=argv) config = snapcraft.yaml.load_config() if args['PART']: config.validate_parts(args['PART']) for part in config.all_parts: if not args['PART'] or part.name in args['PART']: part.clean() # parts dir does not contain only generated code. if (os.path.exists(common.get_partsdir()) and not os.listdir(common.get_partsdir())): os.rmdir(common.get_partsdir()) parts_match = set(config.part_names) == set(args['PART']) # Only clean stage if all the parts were cleaned up. clean_stage = not args['PART'] or parts_match if clean_stage and os.path.exists(common.get_stagedir()): logger.info('Cleaning up staging area') shutil.rmtree(common.get_stagedir()) if os.path.exists(common.get_snapdir()): logger.info('Cleaning up snapping area') shutil.rmtree(common.get_snapdir())
def test_strip_defaults(self): fake_logger = fixtures.FakeLogger(level=logging.ERROR) self.useFixture(fake_logger) parts = self.make_snapcraft_yaml() strip.main() self.assertTrue(os.path.exists(common.get_snapdir()), 'Expected a snap directory') self.assertTrue( os.path.exists( os.path.join(common.get_snapdir(), 'meta', 'snap.yaml')), 'Expected a snap.yaml') self.assertTrue(os.path.exists(common.get_stagedir()), 'Expected a stage directory') self.assertTrue(os.path.exists(common.get_partsdir()), 'Expected a parts directory') self.assertTrue(os.path.exists(parts[0]['part_dir']), 'Expected a part directory for the build0 part') self.assertTrue(os.path.exists(parts[0]['state_file']), 'Expected a state file for the build0 part') with open(parts[0]['state_file']) as sf: state = sf.readlines() self.assertEqual(len(state), 1, 'Expected only one line in the state ' 'file for the build0 part') self.assertEqual(state[0], 'strip', "Expected the state file for " "build0 to be 'strip'")
def test_clean_strip_after_fileset_change(self): # Create part1 and get it through the "build" step. handler = pluginhandler.load_plugin('part1', 'nil') handler.makedirs() bindir = os.path.join(handler.code.installdir, 'bin') os.makedirs(bindir) open(os.path.join(bindir, '1'), 'w').close() open(os.path.join(bindir, '2'), 'w').close() handler.mark_done('build') handler.stage() handler.strip() # Verify that both files have been stripped self.assertTrue( os.path.exists(os.path.join(common.get_snapdir(), 'bin', '1'))) self.assertTrue( os.path.exists(os.path.join(common.get_snapdir(), 'bin', '2'))) # Now update the `snap` fileset to only snap one of these files handler.code.options.snap = ['bin/1'] # Now clean the strip step for part1 handler.clean_strip({}) # Verify that part1's file is no longer stripped self.assertFalse( os.path.exists(os.path.join(common.get_snapdir(), 'bin', '1')), 'Expected bin/1 to be cleaned') self.assertFalse( os.path.exists(os.path.join(common.get_snapdir(), 'bin', '2')), 'Expected bin/2 to be cleaned as well, even though the filesets ' 'changed since it was stripped.')
def test_strip_one_part_only_from_3(self): fake_logger = fixtures.FakeLogger(level=logging.ERROR) self.useFixture(fake_logger) parts = self.make_snapcraft_yaml(n=3) strip.main(['strip1', ]) self.assertFalse( os.path.exists( os.path.join(common.get_snapdir(), 'meta', 'snap.yaml')), 'There should not be a snap.yaml') self.assertTrue(os.path.exists(common.get_snapdir()), 'Expected a snap directory') self.assertTrue(os.path.exists(common.get_stagedir()), 'Expected a stage directory') self.assertTrue(os.path.exists(common.get_partsdir()), 'Expected a parts directory') self.assertTrue(os.path.exists(parts[1]['part_dir']), 'Expected a part directory for the strip1 part') self.assertTrue(os.path.exists(parts[1]['state_file']), 'Expected a state file for the strip1 part') with open(parts[1]['state_file']) as sf: state = sf.readlines() self.assertEqual(len(state), 1, 'Expected only one line in the state ' 'file for the strip1 part') self.assertEqual(state[0], 'strip', "Expected the state file for " " strip1 to be 'strip'") for i in [0, 2]: self.assertFalse(os.path.exists(parts[i]['part_dir']), 'Pulled wrong part') self.assertFalse(os.path.exists(parts[i]['state_file']), 'Expected for only to be a state file for build1')
def test_clean_all(self): cmds.clean({}) self.mock_exists.assert_has_calls([ mock.call('partdir1'), mock.call().__bool__(), mock.call('partdir2'), mock.call().__bool__(), mock.call('partdir3'), mock.call().__bool__(), mock.call(common.get_partsdir()), mock.call().__bool__(), mock.call(common.get_stagedir()), mock.call().__bool__(), mock.call(common.get_snapdir()), mock.call().__bool__(), ]) self.mock_rmtree.assert_has_calls([ mock.call('partdir1'), mock.call('partdir2'), mock.call('partdir3'), mock.call(common.get_stagedir()), mock.call(common.get_snapdir()), ]) self.mock_rmdir.assert_called_once_with(common.get_partsdir())
def clean(args): config = _load_config() part_names = {part.name for part in config.all_parts} for part_name in args.parts: if part_name not in part_names: logger.error('The part named {!r} is not defined in ' '\'snapcraft.yaml\''.format(part_name)) sys.exit(1) for part in config.all_parts: if not args.parts or part.name in args.parts: part.clean() # parts dir does not contain only generated code. if (os.path.exists(common.get_partsdir()) and not os.listdir(common.get_partsdir())): os.rmdir(common.get_partsdir()) clean_stage = not args.parts or part_names == set(args.parts) if clean_stage and os.path.exists(common.get_stagedir()): logger.info('Cleaning up staging area') shutil.rmtree(common.get_stagedir()) if os.path.exists(common.get_snapdir()): logger.info('Cleaning up snapping area') shutil.rmtree(common.get_snapdir())
def test_strip_one_part_only_from_3(self): fake_logger = fixtures.FakeLogger(level=logging.ERROR) self.useFixture(fake_logger) parts = self.make_snapcraft_yaml(n=3) main(['strip', 'strip1']) self.assertFalse( os.path.exists( os.path.join(common.get_snapdir(), 'meta', 'snap.yaml')), 'There should not be a snap.yaml') self.assertTrue(os.path.exists(common.get_snapdir()), 'Expected a snap directory') self.assertTrue(os.path.exists(common.get_stagedir()), 'Expected a stage directory') self.assertTrue(os.path.exists(common.get_partsdir()), 'Expected a parts directory') self.assertTrue(os.path.exists(parts[1]['part_dir']), 'Expected a part directory for the strip1 part') self.verify_state('strip1', parts[1]['state_dir'], 'strip') for i in [0, 2]: self.assertFalse(os.path.exists(parts[i]['part_dir']), 'Pulled wrong part') self.assertFalse(os.path.exists(parts[i]['state_dir']), 'Expected for only to be a state file for build1')
def test_partial_clean(self): parts = self.make_snapcraft_yaml(n=3) clean.main(['clean0', 'clean2']) for i in [0, 2]: self.assertFalse( os.path.exists(parts[i]['part_dir']), 'Expected for {!r} to be wiped'.format(parts[i]['part_dir'])) self.assertTrue(os.path.exists(parts[1]['part_dir']), 'Expected a part directory for the clean1 part') self.assertTrue(os.path.exists(common.get_partsdir())) self.assertTrue(os.path.exists(common.get_stagedir())) self.assertTrue(os.path.exists(common.get_snapdir())) # Now clean it the rest of the way clean.main(['clean1']) for i in range(0, 3): self.assertFalse( os.path.exists(parts[i]['part_dir']), 'Expected for {!r} to be wiped'.format(parts[i]['part_dir'])) self.assertFalse(os.path.exists(common.get_partsdir())) self.assertFalse(os.path.exists(common.get_stagedir())) self.assertFalse(os.path.exists(common.get_snapdir()))
def test_partial_clean(self): parts = self.make_snapcraft_yaml(n=3) main(["clean", "clean0", "clean2"]) for i in [0, 2]: self.assertFalse( os.path.exists(parts[i]["part_dir"]), "Expected for {!r} to be wiped".format(parts[i]["part_dir"]) ) self.assertTrue(os.path.exists(parts[1]["part_dir"]), "Expected a part directory for the clean1 part") self.assertTrue(os.path.exists(common.get_partsdir())) self.assertTrue(os.path.exists(common.get_stagedir())) self.assertTrue(os.path.exists(common.get_snapdir())) # Now clean it the rest of the way main(["clean", "clean1"]) for i in range(0, 3): self.assertFalse( os.path.exists(parts[i]["part_dir"]), "Expected for {!r} to be wiped".format(parts[i]["part_dir"]) ) self.assertFalse(os.path.exists(common.get_partsdir())) self.assertFalse(os.path.exists(common.get_stagedir())) self.assertFalse(os.path.exists(common.get_snapdir()))
def clear_common_directories(self): if os.path.exists(common.get_partsdir()): shutil.rmtree(common.get_partsdir()) if os.path.exists(common.get_stagedir()): shutil.rmtree(common.get_stagedir()) if os.path.exists(common.get_snapdir()): shutil.rmtree(common.get_snapdir())
def test_clean_strip(self): filesets = { 'all': { 'fileset': ['*'], }, 'no1': { 'fileset': ['-1'], }, 'onlya': { 'fileset': ['a'], }, 'onlybase': { 'fileset': ['*', '-*/*'], }, 'nostara': { 'fileset': ['-*/a'], }, } for key, value in filesets.items(): with self.subTest(key=key): self.clear_common_directories() handler = pluginhandler.load_plugin('test_part', 'nil', { 'snap': value['fileset'] }) handler.makedirs() installdir = handler.code.installdir os.makedirs(installdir + '/1/1a/1b') os.makedirs(installdir + '/2/2a') os.makedirs(installdir + '/3') open(installdir + '/a', mode='w').close() open(installdir + '/b', mode='w').close() open(installdir + '/1/a', mode='w').close() open(installdir + '/3/a', mode='w').close() handler.mark_done('build') # Stage the installed files handler.stage() # Now strip them handler.strip() self.assertTrue(os.listdir(common.get_snapdir())) handler.clean_strip({}) self.assertFalse(os.listdir(common.get_snapdir()), 'Expected snapdir to be completely cleaned')
def test_clean_nested_dependent_parts(self): yaml = """name: clean-test version: 1.0 summary: test clean description: test clean parts: main: plugin: nil source: . dependent: plugin: nil source: . after: [main] dependent-dependent: plugin: nil source: . after: [dependent]""" super().make_snapcraft_yaml(yaml) part_dirs = {} for part in ['main', 'dependent', 'dependent-dependent']: part_dirs[part] = os.path.join(common.get_partsdir(), part) os.makedirs(part_dirs[part]) os.makedirs(common.get_stagedir()) os.makedirs(common.get_snapdir()) # Cleaning only `main`. Since `dependent` depends upon main, we expect # that it will be cleaned as well. Otherwise it won't be using the new # `main` when it is built. clean.main(['main']) self.assertFalse(os.path.exists(part_dirs['main']), 'Expected part directory for main to be cleaned') self.assertFalse( os.path.exists(part_dirs['dependent']), 'Expected part directory for dependent to be cleaned as it ' 'depends upon main') self.assertFalse( os.path.exists(part_dirs['dependent-dependent']), 'Expected part directory for dependent-dependent to be cleaned as ' 'it depends upon dependent, which depends upon main') self.assertFalse(os.path.exists(common.get_partsdir())) self.assertFalse(os.path.exists(common.get_stagedir())) self.assertFalse(os.path.exists(common.get_snapdir()))
def test_cleanbuild(self, mock_installed, mock_call): mock_installed.return_value = True fake_logger = fixtures.FakeLogger(level=logging.INFO) self.useFixture(fake_logger) self.make_snapcraft_yaml() # simulate build artifacts dirs = [ os.path.join(common.get_partsdir(), 'part1', 'src'), common.get_stagedir(), common.get_snapdir(), os.path.join(common.get_partsdir(), 'plugins'), ] files_tar = [ os.path.join(common.get_partsdir(), 'plugins', 'x-plugin.py'), 'main.c', ] files_no_tar = [ os.path.join(common.get_stagedir(), 'binary'), os.path.join(common.get_snapdir(), 'binary'), 'snap-test.snap', 'snap-test_1.0_source.tar.bz2', ] for d in dirs: os.makedirs(d) for f in files_tar + files_no_tar: open(f, 'w').close() cleanbuild.main() self.assertEqual( 'Setting up container with project assets\n' 'Waiting for a network connection...\n' 'Network connection established\n' 'Retrieved snap-test_1.0_amd64.snap\n', fake_logger.output) with tarfile.open('snap-test_1.0_source.tar.bz2') as tar: tar_members = tar.getnames() for f in files_no_tar: f = os.path.relpath(f) self.assertFalse('./{}'.format(f) in tar_members, '{} should not be in {}'.format(f, tar_members)) for f in files_tar: f = os.path.relpath(f) self.assertTrue('./{}'.format(f) in tar_members, '{} should be in {}'.format(f, tar_members))
def test_clean_strip_multiple_independent_parts(self): # Create part1 and get it through the "build" step. handler1 = pluginhandler.load_plugin('part1', 'nil') handler1.makedirs() bindir = os.path.join(handler1.code.installdir, 'bin') os.makedirs(bindir) open(os.path.join(bindir, '1'), 'w').close() handler1.mark_done('build') # Now create part2 and get it through the "build" step. handler2 = pluginhandler.load_plugin('part2', 'nil') handler2.makedirs() bindir = os.path.join(handler2.code.installdir, 'bin') os.makedirs(bindir) open(os.path.join(bindir, '2'), 'w').close() handler2.mark_done('build') # Now stage both parts handler1.stage() handler2.stage() # And strip both parts handler1.strip() handler2.strip() # Verify that part1's file has been stripped self.assertTrue( os.path.exists(os.path.join(common.get_snapdir(), 'bin', '1'))) # Verify that part2's file has been stripped self.assertTrue( os.path.exists(os.path.join(common.get_snapdir(), 'bin', '2'))) # Now clean the strip step for part1 handler1.clean_strip({}) # Verify that part1's file is no longer stripped self.assertFalse( os.path.exists(os.path.join(common.get_snapdir(), 'bin', '1')), "Expected part1's stripped files to be cleaned") # Verify that part2's file is still there self.assertTrue( os.path.exists(os.path.join(common.get_snapdir(), 'bin', '2')), "Expected part2's stripped files to be untouched")
def _wrap_exe(relexepath): snap_dir = common.get_snapdir() exepath = os.path.join(snap_dir, relexepath) wrappath = exepath + '.wrapper' # TODO talk to original author if the exception to be captured here is # FileNotFoundError, the original code was a general catch all try: os.remove(wrappath) except FileNotFoundError: pass wrapexec = '$SNAP_APP_PATH/{}'.format(relexepath) if not os.path.exists(exepath) and '/' not in relexepath: # If it doesn't exist it might be in the path logger.debug('Checking to see if "{}" is in the $PATH'.format( relexepath)) with tempfile.NamedTemporaryFile('w+') as tempf: script = ('#!/bin/sh\n' + '{}\n'.format(common.assemble_env()) + 'which "{}"\n'.format(relexepath)) tempf.write(script) tempf.flush() common.run(['/bin/sh', tempf.name], cwd=snap_dir) wrapexec = relexepath _write_wrap_exe(wrapexec, wrappath) return os.path.relpath(wrappath, snap_dir)
def create(config_data, arches=None): ''' Create the meta directory and provision it with package.yaml and readme.md in the snap dir using information from config_data and arches. If provided arches, is a list of arches. Returns meta_dir. ''' # TODO keys for using apparmor, setting an icon missing. meta_dir = os.path.join(common.get_snapdir(), 'meta') os.makedirs(meta_dir, exist_ok=True) config_data['icon'] = _copy(meta_dir, config_data['icon']) if 'framework-policy' in config_data: _copy(meta_dir, config_data['framework-policy'], 'framework-policy') _write_package_yaml(meta_dir, config_data, arches) _write_readme_md(meta_dir, config_data) if 'config' in config_data: _setup_config_hook(meta_dir, config_data['config']) return meta_dir
def _wrap_exe(relexepath): snap_dir = common.get_snapdir() exepath = os.path.join(snap_dir, relexepath) wrappath = exepath + '.wrapper' # TODO talk to original author if the exception to be captured here is # FileNotFoundError, the original code was a general catch all try: os.remove(wrappath) except FileNotFoundError: pass wrapexec = '$SNAP_APP_PATH/{}'.format(relexepath) if not os.path.exists(exepath) and '/' not in relexepath: # If it doesn't exist it might be in the path logger.debug( 'Checking to see if "{}" is in the $PATH'.format(relexepath)) with tempfile.NamedTemporaryFile('w+') as tempf: script = ('#!/bin/sh\n' + '{}\n'.format(common.assemble_env()) + 'which "{}"\n'.format(relexepath)) tempf.write(script) tempf.flush() common.run(['/bin/sh', tempf.name], cwd=snap_dir) wrapexec = relexepath _write_wrap_exe(wrapexec, wrappath) return os.path.relpath(wrappath, snap_dir)
def test_non_shebang_binaries_ignored(self): """Native binaries are ignored. If the executable is a native binary, and thus not have a shebang, it's ignored. """ snapdir = common.get_snapdir() os.mkdir(snapdir) relative_exe_path = 'test_relexepath' # Choose a content which can't be decoded with utf-8, to make # sure no decoding errors happen. exe_contents = b'\xf0\xf1' with open(os.path.join(snapdir, relative_exe_path), 'wb') as exe: exe.write(exe_contents) relative_wrapper_path = meta._wrap_exe(relative_exe_path) wrapper_path = os.path.join(snapdir, relative_wrapper_path) expected = ('#!/bin/sh\n' '\n\n' 'exec "$SNAP/test_relexepath" $*\n') with open(wrapper_path) as wrapper_file: wrapper_contents = wrapper_file.read() self.assertEqual(expected, wrapper_contents) with open(os.path.join(snapdir, relative_exe_path), 'rb') as exe: self.assertEqual(exe_contents, exe.read())
def assemble(args): args.cmd = 'snap' # With all the data in snapcraft.yaml, maybe it's not a good idea to call # snap(args) and just do a snappy build if assemble was explicitly called. snap(args) ticker = '/-\\|' i = 0 with subprocess.Popen(['snappy', 'build', common.get_snapdir()], stdout=subprocess.PIPE, stderr=subprocess.PIPE,) as proc: ret = None if os.isatty(sys.stdout.fileno()): ret = proc.poll() while ret is None: print('\033[1m\rSnapping\033[0m {}'.format(ticker[i]), end='') i = (i+1) % len(ticker) time.sleep(.2) ret = proc.poll() else: print('Snapping ...') ret = proc.wait() print() if ret == 0: print(proc.stdout.read().decode('utf-8')) else: print(proc.stderr.read().decode('utf-8'), file=sys.stderr) sys.exit(ret)
def _write_wrap_exe(wrapexec, wrappath, shebang=None, args=None, cwd=None): args = ' '.join(args) + ' $*' if args else '$*' cwd = 'cd {}'.format(cwd) if cwd else '' snap_dir = common.get_snapdir() assembled_env = common.assemble_env().replace(snap_dir, '$SNAP') replace_path = r'{}/[a-z0-9][a-z0-9+-]*/install'.format( common.get_partsdir()) assembled_env = re.sub(replace_path, '$SNAP', assembled_env) executable = '"{}"'.format(wrapexec) if shebang is not None: new_shebang = re.sub(replace_path, '$SNAP', shebang) if new_shebang != shebang: # If the shebang was pointing to and executable within the # local 'parts' dir, have the wrapper script execute it # directly, since we can't use $SNAP in the shebang itself. executable = '"{}" "{}"'.format(new_shebang, wrapexec) script = ('#!/bin/sh\n' + '{}\n'.format(assembled_env) + '{}\n'.format(cwd) + 'exec {} {}\n'.format(executable, args)) with open(wrappath, 'w+') as f: f.write(script) os.chmod(wrappath, 0o755)
def test_wrap_exe_must_write_wrapper(self, mock_assemble_env): mock_assemble_env.return_value = """\ PATH={0}/part1/install/usr/bin:{0}/part1/install/bin """.format(common.get_partsdir()) snapdir = common.get_snapdir() os.mkdir(snapdir) relative_exe_path = 'test_relexepath' open(os.path.join(snapdir, relative_exe_path), 'w').close() # Check that the wrapper is created even if there is already a file # with the same name. open(os.path.join('snap', 'test_relexepath.wrapper'), 'w').close() relative_wrapper_path = meta._wrap_exe(relative_exe_path) wrapper_path = os.path.join(snapdir, relative_wrapper_path) expected = ('#!/bin/sh\n' 'PATH=$SNAP/usr/bin:$SNAP/bin\n' '\n\n' 'exec "$SNAP/test_relexepath" $*\n') with open(wrapper_path) as wrapper_file: wrapper_contents = wrapper_file.read() self.assertEqual(expected, wrapper_contents)
def _wrap_exe(relexepath, args=None): snap_dir = common.get_snapdir() exepath = os.path.join(snap_dir, relexepath) wrappath = exepath + ".wrapper" shebang = None # TODO talk to original author if the exception to be captured here is # FileNotFoundError, the original code was a general catch all try: os.remove(wrappath) except FileNotFoundError: pass wrapexec = "$SNAP_APP_PATH/{}".format(relexepath) if not os.path.exists(exepath) and "/" not in relexepath: # If it doesn't exist it might be in the path logger.debug('Checking to see if "{}" is in the $PATH'.format(relexepath)) with tempfile.NamedTemporaryFile("w+") as tempf: script = "#!/bin/sh\n" + "{}\n".format(common.assemble_env()) + 'which "{}"\n'.format(relexepath) tempf.write(script) tempf.flush() common.run(["/bin/sh", tempf.name], cwd=snap_dir) wrapexec = relexepath else: with open(exepath, "rb") as exefile: # If the file has a she-bang, the path might be pointing to # the local 'parts' dir. Extract it so that _write_wrap_exe # will have a chance to rewrite it. if exefile.read(2) == b"#!": shebang = exefile.readline().strip().decode("utf-8") _write_wrap_exe(wrapexec, wrappath, shebang=shebang, args=args) return os.path.relpath(wrappath, snap_dir)
def _wrap_exe(command, basename=None): execparts = shlex.split(command) snap_dir = common.get_snapdir() exepath = os.path.join(snap_dir, execparts[0]) if basename: wrappath = os.path.join(snap_dir, basename) + '.wrapper' else: wrappath = exepath + '.wrapper' shebang = None if os.path.exists(wrappath): os.remove(wrappath) wrapexec = '$SNAP/{}'.format(execparts[0]) if not os.path.exists(exepath) and '/' not in execparts[0]: _find_bin(execparts[0], snap_dir) wrapexec = execparts[0] else: with open(exepath, 'rb') as exefile: # If the file has a she-bang, the path might be pointing to # the local 'parts' dir. Extract it so that _write_wrap_exe # will have a chance to rewrite it. if exefile.read(2) == b'#!': shebang = exefile.readline().strip().decode('utf-8') _write_wrap_exe(wrapexec, wrappath, shebang=shebang, args=execparts[1:]) return os.path.relpath(wrappath, snap_dir)
def test_wrap_exe_writes_wrapper_with_basename(self, mock_assemble_env): mock_assemble_env.return_value = """\ PATH={0}/part1/install/usr/bin:{0}/part1/install/bin """.format(common.get_partsdir()) snapdir = common.get_snapdir() os.mkdir(snapdir) relative_exe_path = 'test_relexepath' open(os.path.join(snapdir, relative_exe_path), 'w').close() relative_wrapper_path = meta._wrap_exe( relative_exe_path, basename='new-name') wrapper_path = os.path.join(snapdir, relative_wrapper_path) self.assertEqual(relative_wrapper_path, 'new-name.wrapper') expected = ('#!/bin/sh\n' 'PATH=$SNAP/usr/bin:$SNAP/bin\n' '\n\n' 'exec "$SNAP/test_relexepath" $*\n') with open(wrapper_path) as wrapper_file: wrapper_contents = wrapper_file.read() self.assertEqual(expected, wrapper_contents)
def create(config_data, arches=None): """ Create the meta directory and provision it with package.yaml and readme.md in the snap dir using information from config_data and arches. If provided arches, is a list of arches. Returns meta_dir. """ # TODO keys for using apparmor, setting an icon missing. meta_dir = os.path.join(common.get_snapdir(), "meta") os.makedirs(meta_dir, exist_ok=True) config_data["icon"] = _copy(meta_dir, config_data["icon"]) if "framework-policy" in config_data: _copy(meta_dir, config_data["framework-policy"], "framework-policy") _write_package_yaml(meta_dir, config_data, arches) _write_readme_md(meta_dir, config_data) if "config" in config_data: _setup_config_hook(meta_dir, config_data["config"]) return meta_dir
def test_snap_defaults(self, mock_call): fake_logger = fixtures.FakeLogger(level=logging.INFO) self.useFixture(fake_logger) self.make_snapcraft_yaml() snap.main() self.assertEqual( 'Pulling part1 \n' 'Building part1 \n' 'Staging part1 \n' 'Stripping part1 \n' 'Snapping snap-test_1.0_amd64.snap\n' 'Snapped snap-test_1.0_amd64.snap\n', fake_logger.output) self.assertTrue(os.path.exists(common.get_stagedir()), 'Expected a stage directory') self.assertTrue(self.state_file, 'Expected a state file for the part1 part') with open(self.state_file) as sf: state = sf.readlines() self.assertEqual(len(state), 1, 'Expected only one line in the state ' 'file for the part1 part') self.assertEqual(state[0], 'strip', "Expected the state file for " "part1 to be 'strip'") mock_call.assert_called_once_with([ 'mksquashfs', common.get_snapdir(), 'snap-test_1.0_amd64.snap', '-noappend', '-comp', 'xz'])
def test_non_snap_shebangs_ignored(self): """Shebangs not pointing to the snap's install dir are ignored. If the shebang points to a system executable, there's no need to interfere. """ snapdir = common.get_snapdir() os.mkdir(snapdir) relative_exe_path = 'test_relexepath' exe_contents = '#!/bin/bash\necho hello\n' with open(os.path.join(snapdir, relative_exe_path), 'w') as exe: exe.write(exe_contents) relative_wrapper_path = meta._wrap_exe(relative_exe_path) wrapper_path = os.path.join(snapdir, relative_wrapper_path) expected = ('#!/bin/sh\n' '\n\n' 'exec "$SNAP/test_relexepath" $*\n') with open(wrapper_path) as wrapper_file: wrapper_contents = wrapper_file.read() self.assertEqual(expected, wrapper_contents) with open(os.path.join(snapdir, relative_exe_path), 'r') as exe: self.assertEqual(exe_contents, exe.read())
def _write_wrap_exe(wrapexec, wrappath, shebang=None, args=None, cwd=None): args = ' '.join(args) + ' $*' if args else '$*' cwd = 'cd {}'.format(cwd) if cwd else '' snap_dir = common.get_snapdir() assembled_env = common.assemble_env().replace(snap_dir, '$SNAP_APP_PATH') replace_path = r'{}/[a-z0-9][a-z0-9+-]*/install'.format( common.get_partsdir()) assembled_env = re.sub(replace_path, '$SNAP_APP_PATH', assembled_env) executable = '"{}"'.format(wrapexec) if shebang is not None: new_shebang = re.sub(replace_path, '$SNAP_APP_PATH', shebang) if new_shebang != shebang: # If the shebang was pointing to and executable within the # local 'parts' dir, have the wrapper script execute it # directly, since we can't use $SNAP_APP_PATH in the shebang # itself. executable = '"{}" "{}"'.format(new_shebang, wrapexec) script = ('#!/bin/sh\n' + '{}\n'.format(assembled_env) + '{}\n'.format(cwd) + 'exec {} {}\n'.format(executable, args)) with open(wrappath, 'w+') as f: f.write(script) os.chmod(wrappath, 0o755)
def test_wrap_exe_writes_wrapper_with_basename(self, mock_assemble_env): mock_assemble_env.return_value = """\ PATH={0}/part1/install/usr/bin:{0}/part1/install/bin """.format(common.get_partsdir()) snapdir = common.get_snapdir() os.mkdir(snapdir) relative_exe_path = 'test_relexepath' open(os.path.join(snapdir, relative_exe_path), 'w').close() relative_wrapper_path = meta._wrap_exe(relative_exe_path, basename='new-name') wrapper_path = os.path.join(snapdir, relative_wrapper_path) self.assertEqual(relative_wrapper_path, 'new-name.wrapper') expected = ('#!/bin/sh\n' 'PATH=$SNAP/usr/bin:$SNAP/bin\n' '\n\n' 'exec "$SNAP/test_relexepath" $*\n') with open(wrapper_path) as wrapper_file: wrapper_contents = wrapper_file.read() self.assertEqual(expected, wrapper_contents)
def test_clean_all_when_all_parts_specified(self): self.make_snapcraft_yaml(n=3) clean.main(['clean0', 'clean1', 'clean2']) self.assertFalse(os.path.exists(common.get_partsdir())) self.assertFalse(os.path.exists(common.get_stagedir())) self.assertFalse(os.path.exists(common.get_snapdir()))
def test_clean_all(self): self.make_snapcraft_yaml(n=3) clean.main() self.assertFalse(os.path.exists(common.get_partsdir())) self.assertFalse(os.path.exists(common.get_stagedir())) self.assertFalse(os.path.exists(common.get_snapdir()))
def snap_env(self): root = common.get_snapdir() env = [] env += self.runtime_env(root) for part in self.all_parts: env += part.env(root) return env
def snap_env(self): snapdir = common.get_snapdir() env = [] env += _runtime_env(snapdir) for part in self.all_parts: env += part.env(snapdir) return env
def test_no_parts_defined(self): self.fake_config.all_parts = [] self.mock_load_config.return_value = self.fake_config cmds.clean({}) self.mock_exists.assert_has_calls([ mock.call(common.get_stagedir()), mock.call().__bool__(), mock.call(common.get_snapdir()), mock.call().__bool__(), ]) self.mock_rmtree.assert_has_calls([ mock.call(common.get_stagedir()), mock.call(common.get_snapdir()), ]) self.mock_rmdir.assert_called_once_with(common.get_partsdir())
def clean(args): config = _load_config() for part in config.all_parts: logger.info('Cleaning up for part %r', part.name) if os.path.exists(part.partdir): shutil.rmtree(part.partdir) # parts dir does not contain only generated code. if (os.path.exists(common.get_partsdir()) and not os.listdir(common.get_partsdir())): os.rmdir(common.get_partsdir()) logger.info('Cleaning up staging area') if os.path.exists(common.get_stagedir()): shutil.rmtree(common.get_stagedir()) logger.info('Cleaning up snapping area') if os.path.exists(common.get_snapdir()): shutil.rmtree(common.get_snapdir())
def test_command_does_not_exist(self): snap_dir = common.get_snapdir() common.env = ['PATH={}/bin:$PATH'.format(snap_dir)] os.mkdir(snap_dir) apps = {'app1': {'command': 'command-does-not-exist'}} with self.assertRaises(EnvironmentError) as raised: meta._wrap_apps(apps) self.assertEqual( "The specified command 'command-does-not-exist' defined in 'app1' " "does not exist or is not executable", str(raised.exception))
def test_strip_one_part_only_from_3(self): fake_logger = fixtures.FakeLogger(level=logging.ERROR) self.useFixture(fake_logger) parts = self.make_snapcraft_yaml(n=3) strip.main([ 'strip1', ]) self.assertFalse( os.path.exists( os.path.join(common.get_snapdir(), 'meta', 'package.yaml')), 'There should not be a package.yaml') self.assertTrue(os.path.exists(common.get_snapdir()), 'Expected a snap directory') self.assertTrue(os.path.exists(common.get_stagedir()), 'Expected a stage directory') self.assertTrue(os.path.exists(common.get_partsdir()), 'Expected a parts directory') self.assertTrue(os.path.exists(parts[1]['part_dir']), 'Expected a part directory for the strip1 part') self.assertTrue(os.path.exists(parts[1]['state_file']), 'Expected a state file for the strip1 part') with open(parts[1]['state_file']) as sf: state = sf.readlines() self.assertEqual( len(state), 1, 'Expected only one line in the state ' 'file for the strip1 part') self.assertEqual( state[0], 'strip', "Expected the state file for " " strip1 to be 'strip'") for i in [0, 2]: self.assertFalse(os.path.exists(parts[i]['part_dir']), 'Pulled wrong part') self.assertFalse( os.path.exists(parts[i]['state_file']), 'Expected for only to be a state file for build1')
def snap(args): cmd(args) # This check is to support manual assembly. if not os.path.exists(os.path.join(common.get_snapdir(), 'meta')): arches = [snapcraft.common.get_arch(), ] config = _load_config() # FIXME this should be done in a more contained manner common.env = config.snap_env() meta.create(config.data, arches)
def test_exe_is_in_path(self, run_mock): snapdir = common.get_snapdir() app_path = os.path.join(snapdir, 'bin', 'app1') os.makedirs(os.path.dirname(app_path)) open(app_path, 'w').close() relative_wrapper_path = meta._wrap_exe('app1') wrapper_path = os.path.join(snapdir, relative_wrapper_path) expected = ('#!/bin/sh\n' '\n\n' 'exec "app1" $*\n') with open(wrapper_path) as wrapper_file: wrapper_contents = wrapper_file.read() self.assertEqual(expected, wrapper_contents)
def create(config_data): """Create snap.yaml and necessary package hooks. Create the meta directory and provision it with snap.yaml and hooks in the snap dir using information from config_data. :param dict config_data: project values defined in snapcraft.yaml. :return: meta_dir. """ meta_dir = os.path.join(common.get_snapdir(), 'meta') os.makedirs(meta_dir, exist_ok=True) _write_snap_yaml(meta_dir, config_data) _setup_assets(meta_dir, config_data) return meta_dir
def test_command_is_not_executable(self): snap_dir = common.get_snapdir() common.env = ['PATH={}/bin:$PATH'.format(snap_dir)] apps = {'app1': {'command': 'command-not-executable'}} cmd_path = os.path.join(snap_dir, 'bin', apps['app1']['command']) os.makedirs(os.path.dirname(cmd_path)) open(cmd_path, 'w').close() with self.assertRaises(EnvironmentError) as raised: meta._wrap_apps(apps) self.assertEqual( "The specified command 'command-not-executable' defined in 'app1' " "does not exist or is not executable", str(raised.exception))
def _write_wrap_exe(wrapexec, wrappath, args=[], cwd=None): args = ' '.join(args) + ' $*' if args else '$*' cwd = 'cd {}'.format(cwd) if cwd else '' snap_dir = common.get_snapdir() assembled_env = common.assemble_env().replace(snap_dir, '$SNAP_APP_PATH') replace_path = r'{}/.*/install'.format(common.get_partsdir()) assembled_env = re.sub(replace_path, '$SNAP_APP_PATH', assembled_env) script = ('#!/bin/sh\n' + '{}\n'.format(assembled_env) + '{}\n'.format(cwd) + 'exec "{}" {}\n'.format(wrapexec, args)) with open(wrappath, 'w+') as f: f.write(script) os.chmod(wrappath, 0o755)
def test_everything_is_clean(self): self.mock_exists.return_value = False self.mock_listdir.side_effect = FileNotFoundError() cmds.clean({}) self.mock_exists.assert_has_calls([ mock.call('partdir1'), mock.call('partdir2'), mock.call('partdir3'), mock.call(common.get_partsdir()), mock.call(common.get_stagedir()), mock.call(common.get_snapdir()), ]) self.assertFalse(self.mock_rmdir.called) self.assertFalse(self.mock_rmtree.called)
def test_command_found(self): snap_dir = common.get_snapdir() common.env = ['PATH={}/bin:$PATH'.format(snap_dir)] apps = {'app1': {'command': 'command-executable'}} cmd_path = os.path.join(snap_dir, 'bin', apps['app1']['command']) os.makedirs(os.path.dirname(cmd_path)) open(cmd_path, 'w').close() os.chmod(cmd_path, 0o755) wrapped_apps = meta._wrap_apps(apps) self.assertEqual(wrapped_apps, {'app1': { 'command': 'command-app1.wrapper' }})
def test_wrap_exe_must_write_wrapper(self): snapdir = common.get_snapdir() os.mkdir(snapdir) relative_exe_path = 'test_relexepath' with open(os.path.join(snapdir, relative_exe_path), 'w'): pass relative_wrapper_path = meta._wrap_exe(relative_exe_path) wrapper_path = os.path.join(snapdir, relative_wrapper_path) expected = ('#!/bin/sh\n' '\n\n' 'exec "$SNAP_APP_PATH/test_relexepath" $*\n') with open(wrapper_path) as wrapper_file: wrapper_contents = wrapper_file.read() self.assertEqual(expected, wrapper_contents)
def main(argv=None): argv = argv if argv else [] args = docopt(__doc__, argv=argv) if args['DIRECTORY']: # TODO: migrate to meta/snap.yaml # TODO: write integration test snap_dir = os.path.abspath(args['DIRECTORY']) snap = _snap_data_from_dir(snap_dir) else: # make sure the full lifecycle is executed snap_dir = common.get_snapdir() snap = lifecycle.execute('strip') snap_name = _format_snap_name(snap) logger.info('Snapping {}'.format(snap_name)) subprocess.check_call( ['mksquashfs', snap_dir, snap_name, '-noappend', '-comp', 'xz']) logger.info('Snapped {}'.format(snap_name))
def cmd(args): forceAll = args.force forceCommand = None cmds = [args.cmd] if cmds[0] in common.COMMAND_ORDER: forceCommand = cmds[0] cmds = common.COMMAND_ORDER[0:common.COMMAND_ORDER.index(cmds[0]) + 1] config = _load_config() _install_build_packages(config.build_tools) # clean the snap dir before Snapping snap_clean = False for part in config.all_parts: for cmd in cmds: if cmd is 'stage': # This ends up running multiple times, as each part gets to its # staging cmd. That's inefficient, but largely OK. # FIXME: fix the above by iterating over cmds before iterating # all_parts. But then we need to make sure we continue to # handle cases like go, where you want go built before trying # to pull a go project. if not _check_for_collisions(config.all_parts): sys.exit(1) # We want to make sure we have a clean snap dir if cmd is 'snap' and not snap_clean: shutil.rmtree(common.get_snapdir()) snap_clean = True common.env = config.build_env_for_part(part) force = forceAll or cmd == forceCommand try: getattr(part, cmd)(force=force) except Exception as e: logger.error('Failed doing %s for %s: %s', cmd, part.name, e) sys.exit(1)