def create_build(build_type_dir, config=None, targets=None, extra_args=None): '''Helper to instantiate a Build object from the dynamic generate code Assumes the working directory is alongside src and development :param build_type_dir: currently always "development" :param targets: the targets this build will concern itself with; a value of `None` signifies all targets :type targets: iterable :param extra_args: command line arguments that haven't been consumed yet :type extra_args: sequence ''' generate_dynamic = import_generate_dynamic() if config == None: app_config = build_config.load_app() else: app_config = config local_config = build_config.load_local() extra_args = [] if extra_args is None else extra_args ignore_patterns = _get_ignore_patterns_for_src(defaults.SRC_DIR) enabled_platforms = _enabled_platforms(build_type_dir) if targets is not None: enabled_platforms = set(enabled_platforms) & set(targets) build_to_run = generate_dynamic.build.Build(app_config, defaults.SRC_DIR, build_type_dir, enabled_platforms=enabled_platforms, ignore_patterns=ignore_patterns, local_config=local_config, extra_args=extra_args, forge_root=defaults.FORGE_ROOT) return build_to_run
def set_dotted_attributes(build, unexpanded_local_config_values): """Save a local_config value given in dotted form to the local_config.json for the current build. Example use:: set_dotted_attribute(build, 'android.profile.sdk', '/opt/android-sdk-linux') """ from forge import build_config local_config_values = { expand_profile(build, k): v for k, v in unexpanded_local_config_values.items() } LOG.info('Updating local config with %s' % json.dumps(local_config_values, indent=4)) with cd(build.orig_wd): local_config = build_config.load_local() for attribute_name, value in local_config_values.items(): current_level = local_config crumbs = attribute_name.split('.') for k in crumbs[:-1]: if k not in current_level: current_level[k] = {} current_level = current_level[k] current_level[crumbs[-1]] = value build_config.save_local(local_config)
def test_if_no_config_file_should_return_empty_dict(self): open_file = mock.MagicMock() open_file.side_effect = IOError with mock.patch('forge.build_config.open_file', new=open_file): local_config = build_config.load_local() eq_(local_config, {})
def test_should_return_json_as_dict(self, isfile): isfile.return_value = True open_file = mock.MagicMock() opened_file = open_file.return_value.__enter__.return_value opened_file.read.return_value = '{"provisioning_profile": "dummy pp"}' with mock.patch('forge.build_config.open_file', new=open_file): local_config = build_config.load_local() eq_(local_config, {'provisioning_profile': 'dummy pp'})
def _set_dotted_attribute(path_to_app, attribute_name, value): from forge import build_config LOG.info('Saving %s as %s in local_config.json' % (value, attribute_name)) with cd(path_to_app): local_config = build_config.load_local() current_level = local_config crumbs = attribute_name.split('.') for k in crumbs[:-1]: if k not in current_level: current_level[k] = {} current_level = current_level[k] current_level[crumbs[-1]] = value build_config.save_local(local_config)
def create_build(build_type_dir, config=None, targets=None, extra_args=None, generate_dynamic=None): '''Helper to instantiate a Build object from the dynamic generate code Assumes the working directory is alongside src and development :param build_type_dir: currently always "development" :param targets: the targets this build will concern itself with; a value of `None` signifies all targets :type targets: iterable :param extra_args: command line arguments that haven't been consumed yet :type extra_args: sequence :param generate_dynamic: generate_dynamic module to get Build class from ''' if generate_dynamic is None: # prevent cyclic recursion with import_generate_dynamic generate_dynamic = import_generate_dynamic() if config is None: app_config = build_config.load_app() else: app_config = config local_config = build_config.load_local() extra_args = [] if extra_args is None else extra_args ignore_patterns = _get_ignore_patterns_for_src(defaults.SRC_DIR) enabled_platforms = _enabled_platforms(build_type_dir) if targets is not None: enabled_platforms = set(enabled_platforms) & set(targets) build_to_run = generate_dynamic.build.Build( app_config, defaults. SRC_DIR, # confusingly this is meant to point to platform source code... this value is not used by the customer tasks. build_type_dir, # where the output of build should go enabled_platforms=enabled_platforms, ignore_patterns=ignore_patterns, local_config=local_config, extra_args=extra_args, forge_root=defaults.FORGE_ROOT) return build_to_run
def create_build(build_type_dir, config=None, targets=None, extra_args=None, generate_dynamic=None): '''Helper to instantiate a Build object from the dynamic generate code Assumes the working directory is alongside src and development :param build_type_dir: currently always "development" :param targets: the targets this build will concern itself with; a value of `None` signifies all targets :type targets: iterable :param extra_args: command line arguments that haven't been consumed yet :type extra_args: sequence :param generate_dynamic: generate_dynamic module to get Build class from ''' if generate_dynamic is None: # prevent cyclic recursion with import_generate_dynamic generate_dynamic = import_generate_dynamic() if config is None: app_config = build_config.load_app() else: app_config = config local_config = build_config.load_local() extra_args = [] if extra_args is None else extra_args ignore_patterns = _get_ignore_patterns_for_src(defaults.SRC_DIR) enabled_platforms = _enabled_platforms(build_type_dir) if targets is not None: enabled_platforms = set(enabled_platforms) & set(targets) build_to_run = generate_dynamic.build.Build( app_config, defaults.SRC_DIR, # confusingly this is meant to point to platform source code... this value is not used by the customer tasks. build_type_dir, # where the output of build should go enabled_platforms=enabled_platforms, ignore_patterns=ignore_patterns, local_config=local_config, extra_args=extra_args, forge_root=defaults.FORGE_ROOT ) return build_to_run
def set_dotted_attribute(build, attribute_name, value): """Save a local_config value given in dotted form to the local_config.json for the current build. Example use:: set_dotted_attribute(build, 'android.profiles.DEFAULT.sdk', '/opt/android-sdk-linux') """ # TODO: would be good to not have to import the build_config module here, maybe emit an event instead? from forge import build_config LOG.info('Saving %s as %s in local_config.json' % (value, attribute_name)) with cd(build.orig_wd): local_config = build_config.load_local() current_level = local_config crumbs = attribute_name.split('.') for k in crumbs[:-1]: if k not in current_level: current_level[k] = {} current_level = current_level[k] current_level[crumbs[-1]] = value build_config.save_local(local_config)
def set_dotted_attribute(build, attribute_name, value): """Save a local_config value given in dotted form to the local_config.json for the current build. Example use:: set_dotted_attribute(build, 'android.profiles.DEFAULT.sdk', '/opt/android-sdk-linux') """ # TODO: would be good to not have to import the build_config module here, maybe emit an event instead? from forge import build_config LOG.info("Saving %s as %s in local_config.json" % (value, attribute_name)) with cd(build.orig_wd): local_config = build_config.load_local() current_level = local_config crumbs = attribute_name.split(".") for k in crumbs[:-1]: if k not in current_level: current_level[k] = {} current_level = current_level[k] current_level[crumbs[-1]] = value build_config.save_local(local_config)
def set_dotted_attributes(build, unexpanded_local_config_values): """Save a local_config value given in dotted form to the local_config.json for the current build. Example use:: set_dotted_attribute(build, 'android.profile.sdk', '/opt/android-sdk-linux') """ from forge import build_config local_config_values = {expand_profile(build, k): v for k, v in unexpanded_local_config_values.items()} LOG.info('Updating local config with %s' % json.dumps(local_config_values, indent=4)) with cd(build.orig_wd): local_config = build_config.load_local() for attribute_name, value in local_config_values.items(): current_level = local_config crumbs = attribute_name.split('.') for k in crumbs[:-1]: if k not in current_level: current_level[k] = {} current_level = current_level[k] current_level[crumbs[-1]] = value build_config.save_local(local_config)
def package_web(build): path_to_app = path.abspath('') interactive = build.tool_config.get('general.interactive', True) development = path.abspath(path.join('development', 'web')) output = path.abspath(path.join('release', 'web', 'heroku')) # deploy to Heroku if sys.platform.startswith("win"): heroku = "heroku.bat" else: heroku = "heroku" with cd(development): username = None api_key = build.tool_config.get('web.profile.heroku_api_key') while api_key is None: if not interactive: raise Exception("You need to specify an API Key for interaction with heroku") try: # TODO: may want to check the api key is actually valid by hitting the api? username, api_key = _heroku_credentials() except IOError: login_call = subprocess.call([heroku, 'login']) if login_call != 0: raise Exception("Failed to login with the heroku api") chosen_app = build.tool_config.get('web.profile.heroku_app_name') if not path.isdir(output): os.makedirs(output) with cd(output): if not path.isdir('.git'): LOG.debug('Creating git repo') _git('init') LOG.debug('Create dummy first commit') with open('.forge.txt', 'w') as forge_file: forge_file.write('') _git('add', '.') _git('commit', '-am', '"first commit"') if chosen_app is None: chosen_app = _request_app_to_push_to(build, api_key, interactive) from forge import build_config LOG.info('Saving %s as chosen as web.profiles.%s.heroku_app_name in local_config.json' % (chosen_app, build.tool_config.profile())) with cd(path_to_app): local_config = build_config.load_local() current_level = local_config for k in ['web','profiles', build.tool_config.profile()]: if k not in current_level: current_level[k] = {} current_level = current_level[k] current_level['heroku_app_name'] = chosen_app build_config.save_local(local_config) # remove all previous files/folders except for .git! with cd(output): for f in os.listdir('.'): if not f == '.git': if path.isfile(f): os.remove(f) elif path.isdir(f): shutil.rmtree(f) # copy code from development to release! with cd(development): for f in os.listdir('.'): if path.isfile(f): shutil.copy2(f, output) elif path.isdir(f) and path.basename(f) != '.git': shutil.copytree(f, path.join(output, f), ignore=shutil.ignore_patterns('.git')) with cd(output): # setup with the specified remote LOG.debug('Setting up git remote for %s' % chosen_app) # remove any previous remote try: _git('remote', 'rm', 'heroku') except WebError: pass _git('remote', 'add', 'heroku', '[email protected]:%s.git' % chosen_app) # commit _git('add', '.') diff = _git('diff', 'HEAD') if not diff.strip(): if interactive: LOG.warning("No app changes detected: did you forget to forge build?") else: # not interactive basically means we're using the trigger toolkit, where 'forge build' # doesn't really make sense LOG.warning("No app changes detected, pushing to heroku anyway") else: _git('commit', '-am', 'forge package web') # push LOG.info('Deploying to %s.herokuapp.com' % chosen_app) if not interactive: LOG.warning('You may need to check the commandline to enter an SSH key passphrase') push_output = _git('push', 'heroku', '--all', '--force', command_log_level=logging.INFO) if push_output.startswith('Everything up-to-date'): remote_output = _git('remote', '-v') remote_pattern = re.compile(r'[email protected]:(.*?).git \(fetch\)') remote_match = remote_pattern.search(remote_output) if remote_match: app_url = 'http://%s.herokuapp.com' % remote_match.group(1) _open_url(app_url) LOG.info('Deployed at %s' % app_url) else: deploy_pattern = re.compile(r'(http://[^ ]+) deployed to Heroku') deploy_match = deploy_pattern.search(push_output) if deploy_match: _open_url(deploy_match.group(1)) LOG.info('Deployed at %s' % deploy_match.group(1))