def development_build_dynamic(unhandled_args, has_target, manager, remote, app_config, stable_platform, frepeat):
	
	# Peek at target
	try:
		target = unhandled_args[0]
	except IndexError:
		target = ""

	config_changed = manager.need_new_templates_for_config()
	if config_changed and target in ["ios-native", "android-native"]:
			moved_to = moved_to = os.path.join('development', '%s.%s' % (target, datetime.now().isoformat().replace(":", "-")))
			LOG.warn("")
			LOG.warn("========================================================================")
			LOG.warn("Your application's configuration has changed and the Xcode project needs")
			LOG.warn("to be regenerated.")
			LOG.warn("")
			LOG.warn("Proceeding will reset any changes you've made to the Xcode project and")
			LOG.warn("require you to repopulate your ForgeExtensions/ folder.")	
			LOG.warn("")
			LOG.warn("Your existing Xcode project will be backed up to:")
			LOG.warn("")
			LOG.warn("\t%s" % moved_to)
			LOG.warn("========================================================================")
			time.sleep(1) # cli.ask_yes_no isn't buffered
			proceed = cli.ask_yes_no("Backup Xcode project directory and continue build?", False)
			if not proceed:
				LOG.info("Aborting build and restoring app configuration.")
				shutil.copy(path.join(defaults.TEMPLATE_DIR, "config.json"), path.join(defaults.SRC_DIR, "config.json"))
				LOG.info("App configuration restored.")
				return
			else: # backup existing Xcode directory
				moved_from = path.join('development', target)
				if os.path.exists(moved_from):					
					shutil.move(moved_from, moved_to)
				pass

	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()

		# repeat the whole procedure, as we may have migrated the app in some way
		forge.settings['full'] = False
		return frepeat(unhandled_args, has_target) # TODO This is a really ugly approach

	reload_result = remote.create_buildevent(app_config)['data']
	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(FORGE_BUILD_NEEDS_TARGET)
	except IndexError:
		raise ForgeError(FORGE_BUILD_NEEDS_TARGET)

	# 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(defaults.TEMPLATE_DIR, 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':
		# do nothing: not an issue to be on a minor platform since v2.0.0
		pass

	elif platform_category == 'old':
		LOG.warning("Platform version: %s is no longer the current platform version, it is recommended you switch to a newer version." % 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), symlinks=True)

	if target in ["ios-native", "android-native"]:
			if not os.path.isdir(path.join('development', 'ios-native')):
				try_a_few_times(move_files_across)
			else:
				# Just delete the source files
				shutil.rmtree(customer_phases.locations_normal[target])
				# Delete reload as other targets may build it
				shutil.rmtree(path.join('development', 'reload'), ignore_errors=True)
	else:
		# 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)

	if target in ["ios-native", "android-native"]:
		LOG.info("Development build created. You can access your native project files in the 'development/{target}' directory.".format(
			target=target
		))
	else:
		LOG.info("Development build created. Use {prog} run to run your app.".format(
			prog=ENTRY_POINT_NAME
		))
Example #2
0
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
	))