class TestFetchTemplateAppsAndInstructions(object): def setup(self): self.manager = Manager(dummy_config()) @mock.patch('forge.templates.shutil') @mock.patch('forge.templates.Remote') @mock.patch('forge.templates.tempfile') @mock.patch('forge.templates.import_generate_dynamic') def test_no_templates(self, import_generate_dynamic, tempfile, Remote, shutil): remote = Remote.return_value temp_dir = tempfile.mkdtemp.return_value = 'dummy temp dir' self.manager.fetch_template_apps_and_instructions(-1) temp_templates_dir = path.join(temp_dir, '.template') temp_instructions_dir = path.join(temp_dir, '.template') remote.fetch_unpackaged.assert_called_once_with(-1, to_dir=temp_templates_dir) remote.fetch_generate_instructions.assert_called_once_with(temp_instructions_dir) shutil.rmtree.assert_has_calls( [mock.call('.template', ignore_errors=True), mock.call(temp_dir, ignore_errors=True)] ) shutil.move.assert_called_once_with(temp_templates_dir, '.template') # ensure invalidation of any cached generate_dynamic module import_generate_dynamic.assert_called_once_with(do_reload=True)
class TestNeedNewTemplatesFor(object): def setup(self): self.manager = Manager(dummy_config()) @mock.patch('forge.templates.path') @mock.patch('forge.templates.import_generate_dynamic') def test_no_changes(self, import_generate_dynamic, path): internal_goals = import_generate_dynamic.return_value.internal_goals internal_goals.config_changes_invalidate_templates.return_value = False path.isdir.return_value = True res = self.manager.need_new_templates_for_config() eq_(res, False) @mock.patch('forge.templates.path') @mock.patch('forge.templates.import_generate_dynamic') def test_important_changes(self, import_generate_dynamic, path): internal_goals = import_generate_dynamic.return_value.internal_goals internal_goals.config_changes_invalidate_templates.return_value = True path.isdir.return_value = True res = self.manager.need_new_templates_for_config() eq_(res, True) @mock.patch('forge.templates.path') def test_dir_not_there(self, path): path.isdir.return_value = False res = self.manager.need_new_templates_for_config() eq_(res, True) @mock.patch('forge.templates.path') def test_old_config_not_there(self, path): path.isfile.return_value = False res = self.manager.need_new_templates_for_config() eq_(res, True)
def setup(self): self.manager = Manager(dummy_config())
def development_build(unhandled_args, has_target=True): '''Pull down new version of platform code in a customised build, and create unpacked development add-on. :param has_target: If this is False, just fetch the generation instructions, don't build any targets. ''' _check_working_directory_is_safe() if not os.path.isdir(defaults.SRC_DIR): raise ForgeError( 'Source folder "{src}" does not exist - have you run {prog} create yet?'.format( src=defaults.SRC_DIR, prog=ENTRY_POINT_NAME, ) ) config = build_config.load() remote = Remote(config) remote.check_version() manager = Manager(config) instructions_dir = defaults.INSTRUCTIONS_DIR if forge.settings.get('full', False): # do this first, so that bugs in generate_dynamic can always be nuked with a -f LOG.debug("Full rebuild requested: removing previous templates") shutil.rmtree(instructions_dir, ignore_errors=True) app_config = build_config.load_app() should_rebuild = remote.server_says_should_rebuild() server_changed = should_rebuild['should_rebuild'] reason = should_rebuild['reason'] stable_platform = should_rebuild['stable_platform'] platform_state = should_rebuild['platform_state'] if server_changed: # Need new generate dynamic - download it LOG.debug("Server requires rebuild: {reason}".format(reason=reason)) LOG.info("Your Forge platform has been updated, downloading updated build instructions.") manager.fetch_instructions() config_changed = manager.need_new_templates_for_config() if config_changed: # Need new builds due to local config change LOG.info("Your local config has been changed, downloading updated build instructions.") manager.fetch_instructions() reload_result = remote.create_buildevent(app_config) if not has_target: # No need to go further if we aren't building a target return try: target = unhandled_args.pop(0) if target.startswith("-"): raise ForgeError("Target required for 'forge build'") except IndexError: raise ForgeError("Target required for 'forge build'") # Not all targets output into a folder by the same name. target_dirs = { 'safari': 'forge.safariextension', } target_dir = target if target in target_dirs: target_dir = target_dirs[target] reload_config = json.loads(reload_result['config']) reload_config_hash = reload_result['config_hash'] if target != "reload": # Don't do a server side build for reload if not path.exists(path.join('.template', target_dir)): LOG.info("Your app configuration has changed since your last build of this platform, performing a remote build of your app. Once this is downloaded future builds will be faster.") build = remote.build(config=reload_config, target=target) remote.fetch_unpackaged(build, to_dir=defaults.TEMPLATE_DIR, target=target) else: LOG.info('Config matches previously downloaded build, performing local build.') current_platform = app_config['platform_version'] # Advise user about state of their current platform platform_category = classify_platform(stable_platform, current_platform) if platform_category == 'nonstandard': LOG.warning("Platform version: %s is a non-standard platform version, it may not be receiving updates and it is recommended you update to the stable platform version: %s" % (current_platform, stable_platform)) elif platform_category == 'minor': LOG.warning("Platform version: %s is a minor platform version, it may not be receiving updates, it is recommended you update to a major platform version" % current_platform) elif platform_category == 'old': LOG.warning("Platform version: %s is no longer the current platform version, it is recommended you migrate to a newer version using the 'forge migrate' command. See http://current-docs.trigger.io/release-notes.html for more details" % current_platform) if platform_state == "deprecated": LOG.warning("Platform version: %s is deprecated, it is highly recommended you migrate to a newer version as soon as possible." % current_platform) def move_files_across(): shutil.rmtree(path.join('development', target_dir), ignore_errors=True) if target != "reload": # Delete reload as other targets may build it shutil.rmtree(path.join('development', 'reload'), ignore_errors=True) # No reload server template shutil.copytree(path.join(defaults.TEMPLATE_DIR, target_dir), path.join('development', target_dir)) # Windows often gives a permission error without a small wait try_a_few_times(move_files_across) # Put config hash in config object for local generation # copy first as mutating dict makes assertions about previous uses tricky reload_config_for_local = reload_config.copy() reload_config_for_local['config_hash'] = reload_config_hash # have templates and instructions - inject code generator = Generate() generator.all('development', defaults.SRC_DIR, extra_args=unhandled_args, config=reload_config_for_local, target=target) LOG.info("Development build created. Use {prog} run to run your app.".format( prog=ENTRY_POINT_NAME ))