def test_config_global(west_init_tmpdir): if not os.path.exists(os.path.expanduser('~')): os.mkdir(os.path.expanduser('~')) # To ensure that GLOBAL home folder points into tox temp dir. # Otherwise fail the test, as we don't want to risk manipulating user's # west config assert canon_path(config.ConfigFile.GLOBAL.value) == \ canon_path(os.path.join(os.environ.get('TOXTEMPDIR'), 'pytest-home', '.westconfig')) # Make sure the value is currently unset. testkey_value = cmd('config pytest.testkey_global') assert testkey_value == '' # Set value globally. cmd('config --global pytest.testkey_global foo') # Read from --local, to ensure that is empty. testkey_value = cmd('config --local pytest.testkey_global') assert testkey_value == '' # Read from --system, to ensure that is empty. testkey_value = cmd('config --system pytest.testkey_global') assert testkey_value == '' # Read from --global, and check the value. testkey_value = cmd('config --global pytest.testkey_global') assert testkey_value.rstrip() == 'foo' # Without an explicit config source, the global value (the only # one set) should be returned. testkey_value = cmd('config pytest.testkey_global') assert testkey_value.rstrip() == 'foo'
def _load(self, path_hint): # Initialize this instance's fields from values given in the # manifest data, which must be validated according to the schema. projects = [] projects_by_name = {} projects_by_ppath = {} manifest = self._data['manifest'] # Create the ManifestProject instance and install it into the # projects list. assert MANIFEST_PROJECT_INDEX == 0 projects.append(self._load_self(path_hint)) # Map from each remote's name onto that remote's data in the manifest. self._remotes_by_name = { r['name']: Remote(r['name'], r['url-base']) for r in manifest.get('remotes', []) } # Get any defaults out of the manifest. self._defaults = self._load_defaults() # pdata = project data (dictionary of project information parsed from # the manifest file) for pdata in manifest['projects']: project = self._load_project(pdata) # Project names must be unique. if project.name in projects_by_name: self._malformed('project name {} is already used'.format( project.name)) # Two projects cannot have the same path. We use a PurePath # comparison here to ensure that platform-specific canonicalization # rules are handled correctly. ppath = PurePath(project.path) other = projects_by_ppath.get(ppath) if other: self._malformed( 'project {} path "{}" is already taken by project {}'. format(project.name, project.path, other.name)) else: projects_by_ppath[ppath] = project projects.append(project) projects_by_name[project.name] = project self.projects = tuple(projects) self._projects_by_name = projects_by_name self._projects_by_cpath = {} if self.topdir: mp = self.projects[MANIFEST_PROJECT_INDEX] if mp.abspath: self._projects_by_cpath[util.canon_path(mp.abspath)] = mp for p in self.projects[MANIFEST_PROJECT_INDEX + 1:]: assert p.abspath # sanity check a program invariant self._projects_by_cpath[util.canon_path(p.abspath)] = p
def local(self, args): if args.manifest_rev is not None: log.die('--mr cannot be used with -l') manifest_dir = util.canon_path(args.directory or os.getcwd()) manifest_file = join(manifest_dir, 'west.yml') topdir = dirname(manifest_dir) rel_manifest = basename(manifest_dir) west_dir = os.path.join(topdir, WEST_DIR) log.banner('Initializing from existing manifest repository', rel_manifest) if not exists(manifest_file): log.die('No "west.yml" found in {}'.format(manifest_dir)) self.create(west_dir) os.chdir(topdir) # This validates the manifest. Note we cannot use # self.manifest from west init, as we are in the middle of # creating the installation right now. projects = self.projects(manifest_file) log.small_banner( 'Creating {} and local configuration'.format(west_dir)) update_config('manifest', 'path', rel_manifest) self.topdir = topdir return projects, manifest_dir
def bootstrap(self, args): manifest_url = args.manifest_url or MANIFEST_URL_DEFAULT manifest_rev = args.manifest_rev or MANIFEST_REV_DEFAULT topdir = util.canon_path(args.directory or os.getcwd()) west_dir = join(topdir, WEST_DIR) try: already = util.west_topdir(topdir, fall_back=False) self.die_already(already) except util.WestNotFound: pass log.banner('Initializing in', topdir) if not isdir(topdir): self.create(topdir, exist_ok=False) # Clone the manifest repository into a temporary directory. tempdir = join(west_dir, 'manifest-tmp') if exists(tempdir): log.dbg('removing existing temporary manifest directory', tempdir) shutil.rmtree(tempdir) try: self.clone_manifest(manifest_url, manifest_rev, tempdir) except subprocess.CalledProcessError: shutil.rmtree(tempdir, ignore_errors=True) raise # Verify the manifest file exists. temp_manifest = join(tempdir, 'west.yml') if not exists(temp_manifest): log.die(f'can\'t init: no "west.yml" found in {tempdir}\n' f' Hint: check --manifest-url={manifest_url} and ' '--manifest-rev={manifest_rev}\n' f' You may need to remove {west_dir} before retrying.') # Parse the manifest to get the manifest path, if it declares one. # Otherwise, use the URL. Ignore imports -- all we really # want to know is if there's a "self: path:" or not. projects = Manifest.from_file(temp_manifest, import_flags=ImportFlag.IGNORE, topdir=topdir).projects manifest_project = projects[MANIFEST_PROJECT_INDEX] if manifest_project.path: manifest_path = manifest_project.path else: manifest_path = posixpath.basename(urlparse(manifest_url).path) manifest_abspath = join(topdir, manifest_path) log.dbg('moving', tempdir, 'to', manifest_abspath, level=log.VERBOSE_EXTREME) try: shutil.move(tempdir, manifest_abspath) except shutil.Error as e: log.die(e) log.small_banner('setting manifest.path to', manifest_path) update_config('manifest', 'path', manifest_path, topdir=topdir) return topdir
def get_projects(self, project_ids, allow_paths=True, only_cloned=False): '''Get a list of `Project` objects in the manifest from *project_ids*. If *project_ids* is empty, a copy of ``self.projects`` attribute is returned as a list. Otherwise, the returned list has projects in the same order as *project_ids*. ``ValueError`` is raised if: - *project_ids* contains unknown project IDs - (with *only_cloned*) an uncloned project was found The ``ValueError`` *args* attribute is a 2-tuple with a list of unknown *project_ids* at index 0, and a list of uncloned `Project` objects at index 1. :param project_ids: a sequence of projects, identified by name (these are matched first) or path (as a fallback, but only with *allow_paths*) :param allow_paths: if true, *project_ids* may also contain relative or absolute project paths :param only_cloned: raise an exception for uncloned projects ''' projects = list(self.projects) unknown = [] # all project_ids which don't resolve to a Project uncloned = [] # if only_cloned, resolved Projects which aren't cloned ret = [] # result list of resolved Projects # If no project_ids are specified, use all projects. if not project_ids: if only_cloned: uncloned = [p for p in projects if not p.is_cloned()] if uncloned: raise ValueError(unknown, uncloned) return projects # Otherwise, resolve each of the project_ids to a project, # returning the result or raising ValueError. for pid in project_ids: project = self._projects_by_name.get(pid) if project is None and allow_paths: project = self._projects_by_cpath.get(util.canon_path(pid)) if project is None: unknown.append(pid) else: ret.append(project) if only_cloned and not project.is_cloned(): uncloned.append(project) if unknown or (only_cloned and uncloned): raise ValueError(unknown, uncloned) return ret
def _location(cfg, topdir=None): # Making this a function that gets called each time you ask for a # configuration file makes it respect updated environment # variables (such as XDG_CONFIG_HOME, PROGRAMDATA) if they're set # during the program lifetime. # # Its existence is also relied on in the test cases, to ensure # that the WEST_CONFIG_xyz variables are respected and we're not about # to clobber the user's own configuration files. env = os.environ if cfg == ConfigFile.ALL: raise ValueError('ConfigFile.ALL has no location') elif cfg == ConfigFile.SYSTEM: if 'WEST_CONFIG_SYSTEM' in env: return env['WEST_CONFIG_SYSTEM'] plat = platform.system() if plat == 'Linux': return '/etc/westconfig' elif plat == 'Darwin': return '/usr/local/etc/westconfig' elif plat == 'Windows': return os.path.expandvars('%PROGRAMDATA%\\west\\config') elif 'BSD' in plat: return '/etc/westconfig' elif 'CYGWIN' in plat: # Cygwin can handle windows style paths, so make sure we # return one. We don't want to use os.path.join because # that uses '/' as separator character, and the ProgramData # variable is likely to be something like r'C:\ProgramData'. # # See https://github.com/zephyrproject-rtos/west/issues/300 # for details. pd = pathlib.PureWindowsPath(os.environ['ProgramData']) return str(pd / 'west' / 'config') else: raise ValueError('unsupported platform ' + plat) elif cfg == ConfigFile.GLOBAL: if 'WEST_CONFIG_GLOBAL' in env: return env['WEST_CONFIG_GLOBAL'] elif platform.system() == 'Linux' and 'XDG_CONFIG_HOME' in env: return os.path.join(env['XDG_CONFIG_HOME'], 'west', 'config') else: return canon_path( os.path.join(os.path.expanduser('~'), '.westconfig')) elif cfg == ConfigFile.LOCAL: if topdir: return os.path.join(topdir, '.west', 'config') elif 'WEST_CONFIG_LOCAL' in env: return env['WEST_CONFIG_LOCAL'] else: # Might raise WestNotFound! return os.path.join(west_dir(), 'config') else: raise ValueError(f'invalid configuration file {cfg}')
def config_tmpdir(tmpdir): # Fixture for running from a temporary directory with a .west # inside. We also: # # - ensure we're being run under tox, to avoid messing with # the user's actual configuration files # - ensure configuration files point where they should inside # the temporary tox directories, for the same reason # - ensure ~ exists (since the test environment # doesn't let us run in the true user $HOME). # - set WEST_CONFIG_SYSTEM to lie inside the tmpdir (to avoid # interacting with the real system file) # - set ZEPHYR_BASE (to avoid complaints in subcommand stderr) # # Using this makes the tests run faster than if we used # west_init_tmpdir from conftest.py, and also ensures that the # configuration code doesn't depend on features like the existence # of a manifest file, helping separate concerns. assert 'TOXTEMPDIR' in os.environ, 'you must run tests using tox' toxtmp = os.environ['TOXTEMPDIR'] toxhome = canon_path(os.path.join(toxtmp, 'pytest-home')) global_loc = canon_path(config._location(GLOBAL)) assert canon_path(os.path.expanduser('~')) == toxhome assert global_loc == os.path.join(toxhome, '.westconfig') os.makedirs(toxhome, exist_ok=True) if os.path.exists(global_loc): os.remove(global_loc) tmpdir.mkdir('.west') tmpdir.chdir() # Make sure the 'pytest' section is not present. If it is, # something is wrong in either the test environment (e.g. the user # has a system file with a 'pytest' section in it) or the tests # (if we're not setting ourselves up properly) os.environ['ZEPHYR_BASE'] = str(tmpdir.join('no-zephyr-here')) os.environ['WEST_CONFIG_SYSTEM'] = str(tmpdir.join('config.system')) if 'pytest' in cfg(): del os.environ['ZEPHYR_BASE'] assert False, 'bad fixture setup' yield tmpdir del os.environ['ZEPHYR_BASE'] del os.environ['WEST_CONFIG_SYSTEM']
def _ensure_config(configfile, topdir): # Ensure the given configfile exists, returning its path. May # raise permissions errors, WestNotFound, etc. loc = _location(configfile, topdir=topdir) path = pathlib.Path(loc) if path.is_file(): return loc path.parent.mkdir(parents=True, exist_ok=True) path.touch(exist_ok=True) return canon_path(str(path))
def _location(cfg, topdir=None): # Making this a function that gets called each time you ask for a # configuration file makes it respect updated environment # variables (such as XDG_CONFIG_HOME, PROGRAMDATA) if they're set # during the program lifetime. # # Its existence is also relied on in the test cases, to ensure # that the WEST_CONFIG_xyz variables are respected and we're not about # to clobber the user's own configuration files. env = os.environ if cfg == ConfigFile.ALL: raise ValueError('ConfigFile.ALL has no location') elif cfg == ConfigFile.SYSTEM: if 'WEST_CONFIG_SYSTEM' in env: return env['WEST_CONFIG_SYSTEM'] plat = platform.system() if plat == 'Linux': return '/etc/westconfig' elif plat == 'Darwin': return '/usr/local/etc/westconfig' elif plat == 'Windows': return os.path.expandvars('%PROGRAMDATA%\\west\\config') elif 'BSD' in plat: return '/etc/westconfig' else: raise ValueError('unsupported platform ' + plat) elif cfg == ConfigFile.GLOBAL: if 'WEST_CONFIG_GLOBAL' in env: return env['WEST_CONFIG_GLOBAL'] elif platform.system() == 'Linux' and 'XDG_CONFIG_HOME' in env: return os.path.join(env['XDG_CONFIG_HOME'], 'west', 'config') else: return canon_path( os.path.join(os.path.expanduser('~'), '.westconfig')) elif cfg == ConfigFile.LOCAL: if topdir: return os.path.join(topdir, '.west', 'config') elif 'WEST_CONFIG_LOCAL' in env: return env['WEST_CONFIG_LOCAL'] else: # Might raise WestNotFound! return os.path.join(west_dir(), 'config') else: raise ValueError('invalid configuration file {}'.format(cfg))
def bootstrap(self, args): manifest_url = args.manifest_url or MANIFEST_URL_DEFAULT manifest_rev = args.manifest_rev or MANIFEST_REV_DEFAULT topdir = util.canon_path(args.directory or os.getcwd()) west_dir = join(topdir, WEST_DIR) log.banner('Initializing in', topdir) if not isdir(topdir): self.create(topdir, exist_ok=False) os.chdir(topdir) # Clone the manifest repository into a temporary directory. It's # important that west_dir exists and we're under topdir, or we # won't be able to call self.projects() without error later. tempdir = join(west_dir, 'manifest-tmp') if exists(tempdir): log.dbg('removing existing temporary manifest directory', tempdir) shutil.rmtree(tempdir) try: self.clone_manifest(manifest_url, manifest_rev, tempdir) temp_manifest_file = join(tempdir, 'west.yml') if not exists(temp_manifest_file): log.die('No "west.yml" in manifest repository ({})'.format( tempdir)) projects = self.projects(temp_manifest_file) manifest_project = projects[MANIFEST_PROJECT_INDEX] if manifest_project.path: manifest_path = manifest_project.path manifest_abspath = join(topdir, manifest_path) else: url_path = urlparse(manifest_url).path manifest_path = posixpath.basename(url_path) manifest_abspath = join(topdir, manifest_path) shutil.move(tempdir, manifest_abspath) update_config('manifest', 'path', manifest_path) finally: shutil.rmtree(tempdir, ignore_errors=True) self.topdir = topdir return projects, manifest_project.abspath
def _location(cfg): # Making this a function that gets called each time you ask for a # configuration file makes it respect updated environment # variables (such as XDG_CONFIG_HOME, PROGRAMDATA) if they're set # during the program lifetime. It also makes it easier to # monkey-patch for testing :). env = os.environ if cfg == ConfigFile.ALL: raise ValueError('ConfigFile.ALL has no location') elif cfg == ConfigFile.SYSTEM: if 'WEST_CONFIG_SYSTEM' in os.environ: return os.environ['WEST_CONFIG_SYSTEM'] plat = platform.system() if plat == 'Linux': return '/etc/westconfig' elif plat == 'Darwin': return '/usr/local/etc/westconfig' elif plat == 'Windows': return os.path.expandvars('%PROGRAMDATA%\\west\\config') elif 'BSD' in plat: return '/etc/westconfig' else: raise ValueError('unsupported platform ' + plat) elif cfg == ConfigFile.GLOBAL: if 'WEST_CONFIG_GLOBAL' in os.environ: return os.environ['WEST_CONFIG_GLOBAL'] elif platform.system() == 'Linux' and 'XDG_CONFIG_HOME' in env: return os.path.join(env['XDG_CONFIG_HOME'], 'west', 'config') else: return canon_path( os.path.join(os.path.expanduser('~'), '.westconfig')) elif cfg == ConfigFile.LOCAL: if 'WEST_CONFIG_LOCAL' in os.environ: return os.environ['WEST_CONFIG_LOCAL'] else: # Might raise WestNotFound! return os.path.join(west_dir(), 'config') else: raise ValueError('invalid configuration file {}'.format(cfg))
def local(self, args): if args.manifest_rev is not None: log.die('--mr cannot be used with -l') manifest_dir = util.canon_path(args.directory or os.getcwd()) manifest_file = join(manifest_dir, 'west.yml') topdir = dirname(manifest_dir) rel_manifest = basename(manifest_dir) west_dir = os.path.join(topdir, WEST_DIR) if not exists(manifest_file): log.die(f'can\'t init: no "west.yml" found in {manifest_dir}') log.banner('Initializing from existing manifest repository', rel_manifest) log.small_banner(f'Creating {west_dir} and local configuration file') self.create(west_dir) os.chdir(topdir) update_config('manifest', 'path', rel_manifest) return topdir
def _ensure_config(configfile): # Ensure the given configfile exists, returning its path. May # raise permissions errors, WestNotFound, etc. # # Uses pathlib as this is hard to implement correctly without it. loc = _location(configfile) path = pathlib.Path(loc) if path.is_file(): return loc # Create the directory. We can't use # path.parent.mkdir(..., exist_ok=True) # in Python 3.4, so roughly emulate its behavior. try: path.parent.mkdir(parents=True) except FileExistsError: pass path.touch(exist_ok=True) return canon_path(str(path))
def _load(self, data, topdir, path_hint): # Initialize this instance's fields from values given in the # manifest data, which must be validated according to the schema. projects = [] project_names = set() project_paths = set() # Create the ManifestProject instance and install it into the # Project hierarchy. manifest = data.get('manifest') slf = manifest.get('self', dict()) # the self name is already taken if self.path: # We're parsing a real file on disk. We currently require # that we are able to resolve a topdir. We may lift this # restriction in the future. assert topdir mproj = ManifestProject(path=slf.get('path', path_hint), topdir=topdir, west_commands=slf.get('west-commands')) projects.insert(MANIFEST_PROJECT_INDEX, mproj) # Set the topdir attribute based on the results of the above. self.topdir = topdir # Map from each remote's name onto that remote's data in the manifest. remotes = tuple( Remote(r['name'], r['url-base']) for r in manifest.get('remotes', [])) remotes_dict = {r.name: r for r in remotes} # Get any defaults out of the manifest. # # md = manifest defaults (dictionary with values parsed from # the manifest) md = manifest.get('defaults', dict()) mdrem = md.get('remote') if mdrem: # The default remote name, if provided, must refer to a # well-defined remote. if mdrem not in remotes_dict: self._malformed( 'default remote {} is not defined'.format(mdrem)) default_remote = remotes_dict[mdrem] default_remote_name = mdrem else: default_remote = None default_remote_name = None defaults = Defaults(remote=default_remote, revision=md.get('revision')) # pdata = project data (dictionary of project information parsed from # the manifest file) for pdata in manifest['projects']: # Validate the project name. name = pdata['name'] # Validate the project remote or URL. remote_name = pdata.get('remote') url = pdata.get('url') repo_path = pdata.get('repo-path') if remote_name is None and url is None: if default_remote_name is None: self._malformed( 'project {} has no remote or URL (no default is set)'. format(name)) else: remote_name = default_remote_name if remote_name: if remote_name not in remotes_dict: self._malformed( 'project {} remote {} is not defined'.format( name, remote_name)) remote = remotes_dict[remote_name] else: remote = None # Create the project instance for final checking. try: project = Project(name, defaults, path=pdata.get('path'), clone_depth=pdata.get('clone-depth'), revision=pdata.get('revision'), west_commands=pdata.get('west-commands'), remote=remote, repo_path=repo_path, topdir=topdir, url=url) except ValueError as ve: self._malformed(ve.args[0]) # The name "manifest" cannot be used as a project name; it # is reserved to refer to the manifest repository itself # (e.g. from "west list"). Note that this has not always # been enforced, but it is part of the documentation. if project.name == 'manifest': self._malformed('no project can be named "manifest"') # Project names must be unique. if project.name in project_names: self._malformed('project name {} is already used'.format( project.name)) # Two projects cannot have the same path. We use a PurePath # comparison here to ensure that platform-specific canonicalization # rules are handled correctly. if PurePath(project.path) in project_paths: self._malformed('project {} path {} is already in use'.format( project.name, project.path)) else: project_paths.add(PurePath(project.path)) project_names.add(project.name) projects.append(project) self.defaults = defaults self.remotes = remotes self._remotes_dict = remotes_dict self.projects = tuple(projects) self._proj_name_map = {p.name: p for p in self.projects} pmap = dict() if self.topdir: if mproj.abspath: pmap[util.canon_path(mproj.abspath)] = mproj for p in self.projects[MANIFEST_PROJECT_INDEX + 1:]: assert p.abspath # sanity check a program invariant pmap[util.canon_path(p.abspath)] = p self._proj_canon_path_map = pmap
def get_projects(self, project_ids, allow_paths=True, only_cloned=False): '''Get a list of Projects in the manifest from given *project_ids*. If *project_ids* is empty, a list containing all the manifest's projects is returned. The manifest is present as a project in this list at *MANIFEST_PROJECT_INDEX*, and the other projects follow in the order they appear in the manifest file. Otherwise, projects in the returned list are in the same order as specified in *project_ids*. Either of these situations raises a ValueError: - One or more non-existent projects is in *project_ids* - only_cloned is True, and the returned list would have contained one or more uncloned projects On error, the *args* attribute of the ValueError is a 2-tuple, containing a list of unknown project_ids at index 0, and a list of uncloned Projects at index 1. :param project_ids: A sequence of projects, identified by name (at first priority) or path (as a fallback, when allow_paths=True). :param allow_paths: If False, project_ids must be a sequence of project names only; paths are not allowed. :param only_cloned: If True, ValueError is raised if an uncloned project would have been returned. ''' projects = list(self.projects) unknown = [] # all project_ids which don't resolve to a Project uncloned = [] # if only_cloned, resolved Projects which aren't cloned ret = [] # result list of resolved Projects # If no project_ids are specified, use all projects. if not project_ids: if only_cloned: uncloned = [p for p in projects if not p.is_cloned()] if uncloned: raise ValueError(unknown, uncloned) return projects # Otherwise, resolve each of the project_ids to a project, # returning the result or raising ValueError. for pid in project_ids: project = self._proj_name_map.get(pid) if project is None and allow_paths: project = self._proj_canon_path_map.get(util.canon_path(pid)) if project is None: unknown.append(pid) else: ret.append(project) if only_cloned and not project.is_cloned(): uncloned.append(project) if unknown or (only_cloned and uncloned): raise ValueError(unknown, uncloned) return ret
def _load(self, data): # Initialize this instance's fields from values given in the # manifest data, which must be validated according to the schema. projects = [] project_names = set() project_abspaths = set() manifest = data.get('manifest') path = config.get('manifest', 'path', fallback=None) self_tag = manifest.get('self') if path is None: path = self_tag.get('path') if self_tag else '' west_commands = self_tag.get('west-commands') if self_tag else None project = ManifestProject(path=path, west_commands=west_commands) projects.insert(MANIFEST_PROJECT_INDEX, project) # Map from each remote's name onto that remote's data in the manifest. remotes = tuple(Remote(r['name'], r['url-base']) for r in manifest.get('remotes', [])) remotes_dict = {r.name: r for r in remotes} # Get any defaults out of the manifest. # # md = manifest defaults (dictionary with values parsed from # the manifest) md = manifest.get('defaults', dict()) mdrem = md.get('remote') if mdrem: # The default remote name, if provided, must refer to a # well-defined remote. if mdrem not in remotes_dict: self._malformed('default remote {} is not defined'. format(mdrem)) default_remote = remotes_dict[mdrem] default_remote_name = mdrem else: default_remote = None default_remote_name = None defaults = Defaults(remote=default_remote, revision=md.get('revision')) # mp = manifest project (dictionary with values parsed from # the manifest) for mp in manifest['projects']: # Validate the project name. name = mp['name'] # Validate the project remote or URL. remote_name = mp.get('remote') url = mp.get('url') repo_path = mp.get('repo-path') if remote_name is None and url is None: if default_remote_name is None: self._malformed( 'project {} has no remote or URL (no default is set)'. format(name)) else: remote_name = default_remote_name if remote_name: if remote_name not in remotes_dict: self._malformed('project {} remote {} is not defined'. format(name, remote_name)) remote = remotes_dict[remote_name] else: remote = None # Create the project instance for final checking. try: project = Project(name, defaults, path=mp.get('path'), clone_depth=mp.get('clone-depth'), revision=mp.get('revision'), west_commands=mp.get('west-commands'), remote=remote, repo_path=repo_path, url=url) except ValueError as ve: self._malformed(ve.args[0]) # Project names must be unique. if project.name in project_names: self._malformed('project name {} is already used'. format(project.name)) # Two projects cannot have the same path. We use absolute # paths to check for collisions to ensure paths are # normalized (e.g. for case-insensitive file systems or # in cases like on Windows where / or \ may serve as a # path component separator). if project.abspath in project_abspaths: self._malformed('project {} path {} is already in use'. format(project.name, project.path)) project_names.add(project.name) project_abspaths.add(project.abspath) projects.append(project) self.defaults = defaults self.remotes = remotes self._remotes_dict = remotes_dict self.projects = tuple(projects) self._proj_name_map = {p.name: p for p in self.projects} self._proj_canon_path_map = {util.canon_path(p.abspath): p for p in self.projects}