|
|
|
# Copyright 2017 The Chromium Authors. All rights reserved.
|
|
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
|
|
# found in the LICENSE file.
|
|
|
|
|
|
|
|
import ast
|
|
|
|
import collections
|
|
|
|
from io import StringIO
|
|
|
|
import logging
|
|
|
|
import sys
|
|
|
|
import tokenize
|
|
|
|
|
|
|
|
import gclient_utils
|
|
|
|
from third_party import schema
|
|
|
|
|
|
|
|
# TODO: Should fix these warnings.
|
|
|
|
# pylint: disable=line-too-long
|
|
|
|
|
|
|
|
# git_dependencies migration states. Used within the DEPS file to indicate
|
|
|
|
# the current migration state.
|
|
|
|
DEPS = 'DEPS'
|
|
|
|
SYNC = 'SYNC'
|
|
|
|
SUBMODULES = 'SUBMODULES'
|
|
|
|
|
|
|
|
|
|
|
|
class ConstantString(object):
|
|
|
|
def __init__(self, value):
|
|
|
|
self.value = value
|
|
|
|
|
|
|
|
def __format__(self, format_spec):
|
|
|
|
del format_spec
|
|
|
|
return self.value
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return "Str('" + self.value + "')"
|
|
|
|
|
|
|
|
def __eq__(self, other):
|
|
|
|
if isinstance(other, ConstantString):
|
|
|
|
return self.value == other.value
|
|
|
|
|
|
|
|
return self.value == other
|
|
|
|
|
|
|
|
def __hash__(self):
|
|
|
|
return self.value.__hash__()
|
|
|
|
|
|
|
|
|
|
|
|
class _NodeDict(collections.abc.MutableMapping):
|
|
|
|
"""Dict-like type that also stores information on AST nodes and tokens."""
|
|
|
|
def __init__(self, data=None, tokens=None):
|
|
|
|
self.data = collections.OrderedDict(data or [])
|
|
|
|
self.tokens = tokens
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return str({k: v[0] for k, v in self.data.items()})
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return self.__str__()
|
|
|
|
|
|
|
|
def __getitem__(self, key):
|
|
|
|
return self.data[key][0]
|
|
|
|
|
|
|
|
def __setitem__(self, key, value):
|
|
|
|
self.data[key] = (value, None)
|
|
|
|
|
|
|
|
def __delitem__(self, key):
|
|
|
|
del self.data[key]
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
return iter(self.data)
|
|
|
|
|
|
|
|
def __len__(self):
|
|
|
|
return len(self.data)
|
|
|
|
|
|
|
|
def MoveTokens(self, origin, delta):
|
|
|
|
if self.tokens:
|
|
|
|
new_tokens = {}
|
|
|
|
for pos, token in self.tokens.items():
|
|
|
|
if pos[0] >= origin:
|
|
|
|
pos = (pos[0] + delta, pos[1])
|
|
|
|
token = token[:2] + (pos, ) + token[3:]
|
|
|
|
new_tokens[pos] = token
|
|
|
|
|
|
|
|
for value, node in self.data.values():
|
|
|
|
if node.lineno >= origin:
|
|
|
|
node.lineno += delta
|
|
|
|
if isinstance(value, _NodeDict):
|
|
|
|
value.MoveTokens(origin, delta)
|
|
|
|
|
|
|
|
def GetNode(self, key):
|
|
|
|
return self.data[key][1]
|
|
|
|
|
|
|
|
def SetNode(self, key, value, node):
|
|
|
|
self.data[key] = (value, node)
|
|
|
|
|
|
|
|
|
|
|
|
def _NodeDictSchema(dict_schema):
|
|
|
|
"""Validate dict_schema after converting _NodeDict to a regular dict."""
|
|
|
|
def validate(d):
|
|
|
|
schema.Schema(dict_schema).validate(dict(d))
|
|
|
|
return True
|
|
|
|
|
|
|
|
return validate
|
|
|
|
|
|
|
|
|
|
|
|
# See https://github.com/keleshev/schema for docs how to configure schema.
|
|
|
|
_GCLIENT_DEPS_SCHEMA = _NodeDictSchema({
|
|
|
|
schema.Optional(str):
|
|
|
|
schema.Or(
|
|
|
|
None,
|
|
|
|
str,
|
|
|
|
_NodeDictSchema({
|
|
|
|
# Repo and revision to check out under the path
|
|
|
|
# (same as if no dict was used).
|
|
|
|
'url':
|
|
|
|
schema.Or(None, str),
|
|
|
|
|
|
|
|
# Optional condition string. The dep will only be processed
|
|
|
|
# if the condition evaluates to True.
|
|
|
|
schema.Optional('condition'):
|
|
|
|
str,
|
|
|
|
schema.Optional('dep_type', default='git'):
|
|
|
|
str,
|
|
|
|
}),
|
|
|
|
# CIPD package.
|
|
|
|
_NodeDictSchema({
|
|
|
|
'packages': [_NodeDictSchema({
|
|
|
|
'package': str,
|
|
|
|
'version': str,
|
|
|
|
})],
|
|
|
|
schema.Optional('condition'):
|
|
|
|
str,
|
|
|
|
schema.Optional('dep_type', default='cipd'):
|
|
|
|
str,
|
|
|
|
}),
|
Reland "Reland "Add support for GCS deps""
This is a reland of commit 46d5382f69895a756a26a7326f76ac41dde36920
Whats fixed: Removed the call_google_storage refactor so
that all `import download_from_google_storage` callsites
(in depot_tools and chromium/src) won't be affected.
Instead, gclient.py will import Gsutil from download_from_google_storage.
Original change's description:
> Reland "Add support for GCS deps"
>
> This is a reland of commit 3eedee7b55fe20103a3913f48844765217c837c9
>
> Fixed more imports in download_google_storage.py
>
> Original change's description:
> > Add support for GCS deps
> >
> > Also take out GCS calling logic from download_google_storage and
> > into call_google_storage.
> >
> > GCS deps look like:
> > 'src/third_party/node/linux': {
> > 'dep_type': 'gcs',
> > 'condition': 'checkout_linux',
> > 'bucket': 'chromium-nodejs/20.11.0',
> > 'object_name': '46795170ff5df9831955f163f6966abde581c8af',
> > 'sha256sum': '887504c37404898ca41b896f448ee6d7fc24179d8fb6a4b79d028ab7e1b7153d',
> > },
> >
> > 'src/third_party/llvm-build/Release+Asserts': {
> > 'dep_type': 'gcs',
> > 'condition': 'checkout_linux',
> > 'bucket': 'chromium-browser-clang',
> > 'object_name': 'Linux_x64/clang-llvmorg-18-init-17730-gf670112a-2.tar.xz',
> > 'sha256sum': '1e46df9b4e63c074064d75646310cb76be2f19815997a8486987189d80f991e8',
> > },
> >
> > Example directory for src/third_party/node/linux after gclient sync:
> > - tar_file.gz is the downloaded file from GCS.
> > - node_linux_x64/ is extracted in its path.
> > - `hash` contains the sha of GCS filename.
> > ```
> > chromium/src/ ->
> > third_party/node/linux/ ->
> > hash, tar_file.gz, node_linux_x64/
> > ```
> >
> > Bug: b/324418194
> > Change-Id: Ibcbbff27e211f194ddb8a08494af56570a84a12b
> > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/5299722
> > Commit-Queue: Stephanie Kim <kimstephanie@google.com>
> > Reviewed-by: Joanna Wang <jojwang@chromium.org>
>
> Bug: b/324418194
> Change-Id: Ie64265a86abcec0135408715a45c32a8bb7c7408
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/5346338
> Reviewed-by: Joanna Wang <jojwang@chromium.org>
> Commit-Queue: Stephanie Kim <kimstephanie@google.com>
Bug: b/324418194
Change-Id: I8b58dadbaa740fd9da1fbaf29b3b6ff5ef67fd12
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/5352896
Commit-Queue: Stephanie Kim <kimstephanie@google.com>
Reviewed-by: Joanna Wang <jojwang@chromium.org>
12 months ago
|
|
|
# GCS content.
|
|
|
|
_NodeDictSchema({
|
Allow multiple objects per GCS dep
Allows multiple objects to use the same directory path.
This requires that each object has its own unique hash file and
unique migration file name. All object names must be unique.
Also update download_from_google_storage to check for the unique
migration file name.
Remove ConvertToGcs smoke tests since we're not converting
any git <-> gcs deps.
Example:
```
'src/third_party/llvm-build/Release+Asserts': {
'dep_type': 'gcs',
'condition': 'not llvm_force_head_revision',
'bucket': 'chromium-browser-clang',
'objects': [
{
'object_name': 'Linux_x64/clang-llvmorg-19-init-2941-ga0b3dbaf-22.tar.xz',
'sha256sum': '7b33138d8592199f97d132242d7b3e10f460c5c9655d49a3ad3767218fba7a77',
'size_bytes': 50212876,
},
{
'object_name': 'Linux_x64/llvmobjdump-llvmorg-19-init-2941-ga0b3dbaf-22.tar.xz',
'sha256sum': '14d669650cd212eb0ccb8c34a9e655338dfdee66fe2ecdaa517f6bd607c09a97',
'size_bytes': 5302312,
},
]
},
```
TODO: update .gitignore to search for *_is_first_class and *_hash
Bug: b/324418194
Change-Id: I89d34b06ee24f4c1aa316cd51530ad078e823143
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/5420793
Commit-Queue: Stephanie Kim <kimstephanie@google.com>
Reviewed-by: Joanna Wang <jojwang@chromium.org>
11 months ago
|
|
|
'bucket':
|
|
|
|
str,
|
|
|
|
'objects': [
|
|
|
|
_NodeDictSchema({
|
|
|
|
'object_name':
|
|
|
|
str,
|
|
|
|
'sha256sum':
|
|
|
|
str,
|
|
|
|
'size_bytes':
|
|
|
|
int,
|
|
|
|
'generation':
|
|
|
|
int,
|
|
|
|
schema.Optional('output_file'):
|
|
|
|
str,
|
|
|
|
# The object will only be processed if the condition
|
|
|
|
# evaluates to True. This is AND with the parent condition.
|
|
|
|
schema.Optional('condition'):
|
|
|
|
str,
|
Allow multiple objects per GCS dep
Allows multiple objects to use the same directory path.
This requires that each object has its own unique hash file and
unique migration file name. All object names must be unique.
Also update download_from_google_storage to check for the unique
migration file name.
Remove ConvertToGcs smoke tests since we're not converting
any git <-> gcs deps.
Example:
```
'src/third_party/llvm-build/Release+Asserts': {
'dep_type': 'gcs',
'condition': 'not llvm_force_head_revision',
'bucket': 'chromium-browser-clang',
'objects': [
{
'object_name': 'Linux_x64/clang-llvmorg-19-init-2941-ga0b3dbaf-22.tar.xz',
'sha256sum': '7b33138d8592199f97d132242d7b3e10f460c5c9655d49a3ad3767218fba7a77',
'size_bytes': 50212876,
},
{
'object_name': 'Linux_x64/llvmobjdump-llvmorg-19-init-2941-ga0b3dbaf-22.tar.xz',
'sha256sum': '14d669650cd212eb0ccb8c34a9e655338dfdee66fe2ecdaa517f6bd607c09a97',
'size_bytes': 5302312,
},
]
},
```
TODO: update .gitignore to search for *_is_first_class and *_hash
Bug: b/324418194
Change-Id: I89d34b06ee24f4c1aa316cd51530ad078e823143
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/5420793
Commit-Queue: Stephanie Kim <kimstephanie@google.com>
Reviewed-by: Joanna Wang <jojwang@chromium.org>
11 months ago
|
|
|
})
|
|
|
|
],
|
|
|
|
schema.Optional('condition'):
|
|
|
|
str,
|
|
|
|
schema.Optional('dep_type', default='gcs'):
|
|
|
|
str,
|
Reland "Reland "Add support for GCS deps""
This is a reland of commit 46d5382f69895a756a26a7326f76ac41dde36920
Whats fixed: Removed the call_google_storage refactor so
that all `import download_from_google_storage` callsites
(in depot_tools and chromium/src) won't be affected.
Instead, gclient.py will import Gsutil from download_from_google_storage.
Original change's description:
> Reland "Add support for GCS deps"
>
> This is a reland of commit 3eedee7b55fe20103a3913f48844765217c837c9
>
> Fixed more imports in download_google_storage.py
>
> Original change's description:
> > Add support for GCS deps
> >
> > Also take out GCS calling logic from download_google_storage and
> > into call_google_storage.
> >
> > GCS deps look like:
> > 'src/third_party/node/linux': {
> > 'dep_type': 'gcs',
> > 'condition': 'checkout_linux',
> > 'bucket': 'chromium-nodejs/20.11.0',
> > 'object_name': '46795170ff5df9831955f163f6966abde581c8af',
> > 'sha256sum': '887504c37404898ca41b896f448ee6d7fc24179d8fb6a4b79d028ab7e1b7153d',
> > },
> >
> > 'src/third_party/llvm-build/Release+Asserts': {
> > 'dep_type': 'gcs',
> > 'condition': 'checkout_linux',
> > 'bucket': 'chromium-browser-clang',
> > 'object_name': 'Linux_x64/clang-llvmorg-18-init-17730-gf670112a-2.tar.xz',
> > 'sha256sum': '1e46df9b4e63c074064d75646310cb76be2f19815997a8486987189d80f991e8',
> > },
> >
> > Example directory for src/third_party/node/linux after gclient sync:
> > - tar_file.gz is the downloaded file from GCS.
> > - node_linux_x64/ is extracted in its path.
> > - `hash` contains the sha of GCS filename.
> > ```
> > chromium/src/ ->
> > third_party/node/linux/ ->
> > hash, tar_file.gz, node_linux_x64/
> > ```
> >
> > Bug: b/324418194
> > Change-Id: Ibcbbff27e211f194ddb8a08494af56570a84a12b
> > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/5299722
> > Commit-Queue: Stephanie Kim <kimstephanie@google.com>
> > Reviewed-by: Joanna Wang <jojwang@chromium.org>
>
> Bug: b/324418194
> Change-Id: Ie64265a86abcec0135408715a45c32a8bb7c7408
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/5346338
> Reviewed-by: Joanna Wang <jojwang@chromium.org>
> Commit-Queue: Stephanie Kim <kimstephanie@google.com>
Bug: b/324418194
Change-Id: I8b58dadbaa740fd9da1fbaf29b3b6ff5ef67fd12
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/5352896
Commit-Queue: Stephanie Kim <kimstephanie@google.com>
Reviewed-by: Joanna Wang <jojwang@chromium.org>
12 months ago
|
|
|
}),
|
|
|
|
),
|
|
|
|
})
|
|
|
|
|
|
|
|
_GCLIENT_HOOKS_SCHEMA = [
|
|
|
|
_NodeDictSchema({
|
|
|
|
# Hook action: list of command-line arguments to invoke.
|
|
|
|
'action': [schema.Or(str)],
|
|
|
|
|
|
|
|
# Name of the hook. Doesn't affect operation.
|
|
|
|
schema.Optional('name'):
|
|
|
|
str,
|
|
|
|
|
|
|
|
# Hook pattern (regex). Originally intended to limit some hooks to run
|
|
|
|
# only when files matching the pattern have changed. In practice, with
|
|
|
|
# git, gclient runs all the hooks regardless of this field.
|
|
|
|
schema.Optional('pattern'):
|
|
|
|
str,
|
|
|
|
|
|
|
|
# Working directory where to execute the hook.
|
|
|
|
schema.Optional('cwd'):
|
|
|
|
str,
|
|
|
|
|
|
|
|
# Optional condition string. The hook will only be run
|
|
|
|
# if the condition evaluates to True.
|
|
|
|
schema.Optional('condition'):
|
|
|
|
str,
|
|
|
|
})
|
|
|
|
]
|
|
|
|
|
|
|
|
_GCLIENT_SCHEMA = schema.Schema(
|
|
|
|
_NodeDictSchema({
|
|
|
|
# Current state of the git submodule migration.
|
|
|
|
# git_dependencies = [DEPS (default) | SUBMODULES | SYNC]
|
|
|
|
schema.Optional('git_dependencies'):
|
|
|
|
schema.Or(DEPS, SYNC, SUBMODULES),
|
|
|
|
|
|
|
|
# List of host names from which dependencies are allowed (allowlist).
|
|
|
|
# NOTE: when not present, all hosts are allowed.
|
|
|
|
# NOTE: scoped to current DEPS file, not recursive.
|
|
|
|
schema.Optional('allowed_hosts'): [schema.Optional(str)],
|
|
|
|
|
|
|
|
# Mapping from paths to repo and revision to check out under that path.
|
|
|
|
# Applying this mapping to the on-disk checkout is the main purpose
|
|
|
|
# of gclient, and also why the config file is called DEPS.
|
|
|
|
#
|
|
|
|
# The following functions are allowed:
|
|
|
|
#
|
|
|
|
# Var(): allows variable substitution (either from 'vars' dict below,
|
|
|
|
# or command-line override)
|
|
|
|
schema.Optional('deps'):
|
|
|
|
_GCLIENT_DEPS_SCHEMA,
|
|
|
|
|
|
|
|
# Similar to 'deps' (see above) - also keyed by OS (e.g. 'linux').
|
|
|
|
# Also see 'target_os'.
|
|
|
|
schema.Optional('deps_os'):
|
|
|
|
_NodeDictSchema({
|
|
|
|
schema.Optional(str): _GCLIENT_DEPS_SCHEMA,
|
|
|
|
}),
|
|
|
|
|
|
|
|
# Dependency to get gclient_gn_args* settings from. This allows these
|
|
|
|
# values to be set in a recursedeps file, rather than requiring that
|
|
|
|
# they exist in the top-level solution.
|
|
|
|
schema.Optional('gclient_gn_args_from'):
|
|
|
|
str,
|
|
|
|
|
|
|
|
# Path to GN args file to write selected variables.
|
|
|
|
schema.Optional('gclient_gn_args_file'):
|
|
|
|
str,
|
|
|
|
|
|
|
|
# Subset of variables to write to the GN args file (see above).
|
|
|
|
schema.Optional('gclient_gn_args'): [schema.Optional(str)],
|
|
|
|
|
|
|
|
# Hooks executed after gclient sync (unless suppressed), or explicitly
|
|
|
|
# on gclient hooks. See _GCLIENT_HOOKS_SCHEMA for details.
|
|
|
|
# Also see 'pre_deps_hooks'.
|
|
|
|
schema.Optional('hooks'):
|
|
|
|
_GCLIENT_HOOKS_SCHEMA,
|
|
|
|
|
|
|
|
# Similar to 'hooks', also keyed by OS.
|
|
|
|
schema.Optional('hooks_os'):
|
|
|
|
_NodeDictSchema({schema.Optional(str): _GCLIENT_HOOKS_SCHEMA}),
|
|
|
|
|
|
|
|
# Rules which #includes are allowed in the directory.
|
|
|
|
# Also see 'skip_child_includes' and 'specific_include_rules'.
|
|
|
|
schema.Optional('include_rules'): [schema.Optional(str)],
|
|
|
|
|
|
|
|
# Commits that add include_rules entries on the paths with this set
|
|
|
|
# will require an OWNERS review from them.
|
|
|
|
schema.Optional('new_usages_require_review'):
|
|
|
|
bool,
|
|
|
|
|
|
|
|
# Optionally discards rules from parent directories, similar to
|
|
|
|
# "noparent" in OWNERS files. For example, if
|
|
|
|
# //base/allocator/partition_allocator has "noparent = True" then it
|
|
|
|
# will not inherit rules from //base/DEPS and //base/allocator/DEPS,
|
|
|
|
# forcing each //base/allocator/partition_allocator/{foo,bar,...} to
|
|
|
|
# declare all its dependencies.
|
|
|
|
schema.Optional('noparent'):
|
|
|
|
bool,
|
|
|
|
|
|
|
|
# Hooks executed before processing DEPS. See 'hooks' for more details.
|
|
|
|
schema.Optional('pre_deps_hooks'):
|
|
|
|
_GCLIENT_HOOKS_SCHEMA,
|
|
|
|
|
|
|
|
# Recursion limit for nested DEPS.
|
|
|
|
schema.Optional('recursion'):
|
|
|
|
int,
|
|
|
|
|
|
|
|
# Allowlists deps for which recursion should be enabled.
|
|
|
|
schema.Optional('recursedeps'): [
|
|
|
|
schema.Optional(schema.Or(str, (str, str), [str, str])),
|
|
|
|
],
|
|
|
|
|
|
|
|
# Blocklists directories for checking 'include_rules'.
|
|
|
|
schema.Optional('skip_child_includes'): [schema.Optional(str)],
|
|
|
|
|
|
|
|
# Mapping from paths to include rules specific for that path.
|
|
|
|
# See 'include_rules' for more details.
|
|
|
|
schema.Optional('specific_include_rules'):
|
|
|
|
_NodeDictSchema({schema.Optional(str): [str]}),
|
|
|
|
|
|
|
|
# List of additional OS names to consider when selecting dependencies
|
|
|
|
# from deps_os.
|
|
|
|
schema.Optional('target_os'): [schema.Optional(str)],
|
|
|
|
|
|
|
|
# For recursed-upon sub-dependencies, check out their own dependencies
|
|
|
|
# relative to the parent's path, rather than relative to the .gclient
|
|
|
|
# file.
|
|
|
|
schema.Optional('use_relative_paths'):
|
|
|
|
bool,
|
|
|
|
|
|
|
|
# For recursed-upon sub-dependencies, run their hooks relative to the
|
|
|
|
# parent's path instead of relative to the .gclient file.
|
|
|
|
schema.Optional('use_relative_hooks'):
|
|
|
|
bool,
|
|
|
|
|
|
|
|
# Variables that can be referenced using Var() - see 'deps'.
|
|
|
|
schema.Optional('vars'):
|
|
|
|
_NodeDictSchema({
|
|
|
|
schema.Optional(str):
|
|
|
|
schema.Or(ConstantString, str, bool),
|
|
|
|
}),
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
|
|
def _gclient_eval(node_or_string, filename='<unknown>', vars_dict=None):
|
|
|
|
"""Safely evaluates a single expression. Returns the result."""
|
|
|
|
_allowed_names = {'None': None, 'True': True, 'False': False}
|
|
|
|
if isinstance(node_or_string, ConstantString):
|
|
|
|
return node_or_string.value
|
|
|
|
if isinstance(node_or_string, str):
|
|
|
|
node_or_string = ast.parse(node_or_string,
|
|
|
|
filename=filename,
|
|
|
|
mode='eval')
|
|
|
|
if isinstance(node_or_string, ast.Expression):
|
|
|
|
node_or_string = node_or_string.body
|
|
|
|
|
|
|
|
def _convert(node):
|
|
|
|
if isinstance(node, ast.Str):
|
|
|
|
if vars_dict is None:
|
|
|
|
return node.s
|
|
|
|
try:
|
|
|
|
return node.s.format(**vars_dict)
|
|
|
|
except KeyError as e:
|
|
|
|
raise KeyError(
|
|
|
|
'%s was used as a variable, but was not declared in the vars dict '
|
|
|
|
'(file %r, line %s)' %
|
|
|
|
(e.args[0], filename, getattr(node, 'lineno', '<unknown>')))
|
|
|
|
elif isinstance(node, ast.Num):
|
|
|
|
return node.n
|
|
|
|
elif isinstance(node, ast.Tuple):
|
|
|
|
return tuple(map(_convert, node.elts))
|
|
|
|
elif isinstance(node, ast.List):
|
|
|
|
return list(map(_convert, node.elts))
|
|
|
|
elif isinstance(node, ast.Dict):
|
|
|
|
node_dict = _NodeDict()
|
|
|
|
for key_node, value_node in zip(node.keys, node.values):
|
|
|
|
key = _convert(key_node)
|
|
|
|
if key in node_dict:
|
|
|
|
raise ValueError(
|
|
|
|
'duplicate key in dictionary: %s (file %r, line %s)' %
|
|
|
|
(key, filename, getattr(key_node, 'lineno',
|
|
|
|
'<unknown>')))
|
|
|
|
node_dict.SetNode(key, _convert(value_node), value_node)
|
|
|
|
return node_dict
|
|
|
|
elif isinstance(node, ast.Name):
|
|
|
|
if node.id not in _allowed_names:
|
|
|
|
raise ValueError(
|
|
|
|
'invalid name %r (file %r, line %s)' %
|
|
|
|
(node.id, filename, getattr(node, 'lineno', '<unknown>')))
|
|
|
|
return _allowed_names[node.id]
|
|
|
|
elif not sys.version_info[:2] < (3, 4) and isinstance(
|
|
|
|
node, ast.NameConstant): # Since Python 3.4
|
|
|
|
return node.value
|
|
|
|
elif isinstance(node, ast.Call):
|
|
|
|
if (not isinstance(node.func, ast.Name)
|
|
|
|
or (node.func.id not in ('Str', 'Var'))):
|
|
|
|
raise ValueError(
|
|
|
|
'Str and Var are the only allowed functions (file %r, line %s)'
|
|
|
|
% (filename, getattr(node, 'lineno', '<unknown>')))
|
|
|
|
if node.keywords or getattr(node, 'starargs', None) or getattr(
|
|
|
|
node, 'kwargs', None) or len(node.args) != 1:
|
|
|
|
raise ValueError(
|
|
|
|
'%s takes exactly one argument (file %r, line %s)' %
|
|
|
|
(node.func.id, filename, getattr(node, 'lineno',
|
|
|
|
'<unknown>')))
|
|
|
|
|
|
|
|
if node.func.id == 'Str':
|
|
|
|
if isinstance(node.args[0], ast.Str):
|
|
|
|
return ConstantString(node.args[0].s)
|
|
|
|
raise ValueError(
|
|
|
|
'Passed a non-string to Str() (file %r, line%s)' %
|
|
|
|
(filename, getattr(node, 'lineno', '<unknown>')))
|
|
|
|
|
|
|
|
arg = _convert(node.args[0])
|
|
|
|
if not isinstance(arg, str):
|
|
|
|
raise ValueError(
|
|
|
|
'Var\'s argument must be a variable name (file %r, line %s)'
|
|
|
|
% (filename, getattr(node, 'lineno', '<unknown>')))
|
|
|
|
if vars_dict is None:
|
|
|
|
return '{' + arg + '}'
|
|
|
|
if arg not in vars_dict:
|
|
|
|
raise KeyError(
|
|
|
|
'%s was used as a variable, but was not declared in the vars dict '
|
|
|
|
'(file %r, line %s)' %
|
|
|
|
(arg, filename, getattr(node, 'lineno', '<unknown>')))
|
|
|
|
val = vars_dict[arg]
|
|
|
|
if isinstance(val, ConstantString):
|
|
|
|
val = val.value
|
|
|
|
return val
|
|
|
|
elif isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add):
|
|
|
|
return _convert(node.left) + _convert(node.right)
|
|
|
|
elif isinstance(node, ast.BinOp) and isinstance(node.op, ast.Mod):
|
|
|
|
return _convert(node.left) % _convert(node.right)
|
|
|
|
else:
|
|
|
|
raise ValueError('unexpected AST node: %s %s (file %r, line %s)' %
|
|
|
|
(node, ast.dump(node), filename,
|
|
|
|
getattr(node, 'lineno', '<unknown>')))
|
|
|
|
|
|
|
|
return _convert(node_or_string)
|
|
|
|
|
|
|
|
|
|
|
|
def Exec(content, filename='<unknown>', vars_override=None, builtin_vars=None):
|
|
|
|
"""Safely execs a set of assignments."""
|
|
|
|
def _validate_statement(node, local_scope):
|
|
|
|
if not isinstance(node, ast.Assign):
|
|
|
|
raise ValueError('unexpected AST node: %s %s (file %r, line %s)' %
|
|
|
|
(node, ast.dump(node), filename,
|
|
|
|
getattr(node, 'lineno', '<unknown>')))
|
|
|
|
|
|
|
|
if len(node.targets) != 1:
|
|
|
|
raise ValueError(
|
|
|
|
'invalid assignment: use exactly one target (file %r, line %s)'
|
|
|
|
% (filename, getattr(node, 'lineno', '<unknown>')))
|
|
|
|
|
|
|
|
target = node.targets[0]
|
|
|
|
if not isinstance(target, ast.Name):
|
|
|
|
raise ValueError(
|
|
|
|
'invalid assignment: target should be a name (file %r, line %s)'
|
|
|
|
% (filename, getattr(node, 'lineno', '<unknown>')))
|
|
|
|
if target.id in local_scope:
|
|
|
|
raise ValueError(
|
|
|
|
'invalid assignment: overrides var %r (file %r, line %s)' %
|
|
|
|
(target.id, filename, getattr(node, 'lineno', '<unknown>')))
|
|
|
|
|
|
|
|
node_or_string = ast.parse(content, filename=filename, mode='exec')
|
|
|
|
if isinstance(node_or_string, ast.Expression):
|
|
|
|
node_or_string = node_or_string.body
|
|
|
|
|
|
|
|
if not isinstance(node_or_string, ast.Module):
|
|
|
|
raise ValueError('unexpected AST node: %s %s (file %r, line %s)' %
|
|
|
|
(node_or_string, ast.dump(node_or_string), filename,
|
|
|
|
getattr(node_or_string, 'lineno', '<unknown>')))
|
|
|
|
|
|
|
|
statements = {}
|
|
|
|
for statement in node_or_string.body:
|
|
|
|
_validate_statement(statement, statements)
|
|
|
|
statements[statement.targets[0].id] = statement.value
|
|
|
|
|
|
|
|
tokens = {
|
|
|
|
token[2]: list(token)
|
|
|
|
for token in tokenize.generate_tokens(StringIO(content).readline)
|
|
|
|
}
|
|
|
|
|
|
|
|
local_scope = _NodeDict({}, tokens)
|
|
|
|
|
|
|
|
# Process vars first, so we can expand variables in the rest of the DEPS
|
|
|
|
# file.
|
|
|
|
vars_dict = {}
|
|
|
|
if 'vars' in statements:
|
|
|
|
vars_statement = statements['vars']
|
|
|
|
value = _gclient_eval(vars_statement, filename)
|
|
|
|
local_scope.SetNode('vars', value, vars_statement)
|
|
|
|
# Update the parsed vars with the overrides, but only if they are
|
|
|
|
# already present (overrides do not introduce new variables).
|
|
|
|
vars_dict.update(value)
|
|
|
|
|
|
|
|
if builtin_vars:
|
|
|
|
vars_dict.update(builtin_vars)
|
|
|
|
|
|
|
|
if vars_override:
|
|
|
|
vars_dict.update(
|
|
|
|
{k: v
|
|
|
|
for k, v in vars_override.items() if k in vars_dict})
|
|
|
|
|
|
|
|
for name, node in statements.items():
|
|
|
|
value = _gclient_eval(node, filename, vars_dict)
|
|
|
|
local_scope.SetNode(name, value, node)
|
|
|
|
|
|
|
|
try:
|
|
|
|
return _GCLIENT_SCHEMA.validate(local_scope)
|
|
|
|
except schema.SchemaError as e:
|
|
|
|
raise gclient_utils.Error(str(e))
|
|
|
|
|
|
|
|
|
|
|
|
def _StandardizeDeps(deps_dict, vars_dict):
|
|
|
|
""""Standardizes the deps_dict.
|
|
|
|
|
|
|
|
For each dependency:
|
|
|
|
- Expands the variable in the dependency name.
|
|
|
|
- Ensures the dependency is a dictionary.
|
|
|
|
- Set's the 'dep_type' to be 'git' by default.
|
|
|
|
"""
|
|
|
|
new_deps_dict = {}
|
|
|
|
for dep_name, dep_info in deps_dict.items():
|
|
|
|
dep_name = dep_name.format(**vars_dict)
|
|
|
|
if not isinstance(dep_info, collections.abc.Mapping):
|
|
|
|
dep_info = {'url': dep_info}
|
|
|
|
dep_info.setdefault('dep_type', 'git')
|
|
|
|
new_deps_dict[dep_name] = dep_info
|
|
|
|
return new_deps_dict
|
|
|
|
|
|
|
|
|
|
|
|
def _MergeDepsOs(deps_dict, os_deps_dict, os_name):
|
|
|
|
"""Merges the deps in os_deps_dict into conditional dependencies in deps_dict.
|
|
|
|
|
|
|
|
The dependencies in os_deps_dict are transformed into conditional dependencies
|
|
|
|
using |'checkout_' + os_name|.
|
|
|
|
If the dependency is already present, the URL and revision must coincide.
|
|
|
|
"""
|
|
|
|
for dep_name, dep_info in os_deps_dict.items():
|
|
|
|
# Make this condition very visible, so it's not a silent failure.
|
|
|
|
# It's unclear how to support None override in deps_os.
|
|
|
|
if dep_info['url'] is None:
|
|
|
|
logging.error('Ignoring %r:%r in %r deps_os', dep_name, dep_info,
|
|
|
|
os_name)
|
|
|
|
continue
|
|
|
|
|
|
|
|
os_condition = 'checkout_' + (os_name if os_name != 'unix' else 'linux')
|
|
|
|
UpdateCondition(dep_info, 'and', os_condition)
|
|
|
|
|
|
|
|
if dep_name in deps_dict:
|
|
|
|
if deps_dict[dep_name]['url'] != dep_info['url']:
|
|
|
|
raise gclient_utils.Error(
|
|
|
|
'Value from deps_os (%r; %r: %r) conflicts with existing deps '
|
|
|
|
'entry (%r).' %
|
|
|
|
(os_name, dep_name, dep_info, deps_dict[dep_name]))
|
|
|
|
|
|
|
|
UpdateCondition(dep_info, 'or',
|
|
|
|
deps_dict[dep_name].get('condition'))
|
|
|
|
|
|
|
|
deps_dict[dep_name] = dep_info
|
|
|
|
|
|
|
|
|
|
|
|
def UpdateCondition(info_dict, op, new_condition):
|
|
|
|
"""Updates info_dict's condition with |new_condition|.
|
|
|
|
|
|
|
|
An absent value is treated as implicitly True.
|
|
|
|
"""
|
|
|
|
curr_condition = info_dict.get('condition')
|
|
|
|
# Easy case: Both are present.
|
|
|
|
if curr_condition and new_condition:
|
|
|
|
info_dict['condition'] = '(%s) %s (%s)' % (curr_condition, op,
|
|
|
|
new_condition)
|
|
|
|
# If |op| == 'and', and at least one condition is present, then use it.
|
|
|
|
elif op == 'and' and (curr_condition or new_condition):
|
|
|
|
info_dict['condition'] = curr_condition or new_condition
|
|
|
|
# Otherwise, no condition should be set
|
|
|
|
elif curr_condition:
|
|
|
|
del info_dict['condition']
|
|
|
|
|
|
|
|
|
|
|
|
def Parse(content, filename, vars_override=None, builtin_vars=None):
|
|
|
|
"""Parses DEPS strings.
|
|
|
|
|
|
|
|
Executes the Python-like string stored in content, resulting in a Python
|
|
|
|
dictionary specified by the schema above. Supports syntax validation and
|
|
|
|
variable expansion.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
content: str. DEPS file stored as a string.
|
|
|
|
filename: str. The name of the DEPS file, or a string describing the source
|
|
|
|
of the content, e.g. '<string>', '<unknown>'.
|
|
|
|
vars_override: dict, optional. A dictionary with overrides for the variables
|
|
|
|
defined by the DEPS file.
|
|
|
|
builtin_vars: dict, optional. A dictionary with variables that are provided
|
|
|
|
by default.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
A Python dict with the parsed contents of the DEPS file, as specified by the
|
|
|
|
schema above.
|
|
|
|
"""
|
|
|
|
result = Exec(content, filename, vars_override, builtin_vars)
|
|
|
|
|
|
|
|
vars_dict = result.get('vars', {})
|
|
|
|
if 'deps' in result:
|
|
|
|
result['deps'] = _StandardizeDeps(result['deps'], vars_dict)
|
|
|
|
|
|
|
|
if 'deps_os' in result:
|
|
|
|
deps = result.setdefault('deps', {})
|
|
|
|
for os_name, os_deps in result['deps_os'].items():
|
|
|
|
os_deps = _StandardizeDeps(os_deps, vars_dict)
|
|
|
|
_MergeDepsOs(deps, os_deps, os_name)
|
|
|
|
del result['deps_os']
|
|
|
|
|
|
|
|
if 'hooks_os' in result:
|
|
|
|
hooks = result.setdefault('hooks', [])
|
|
|
|
for os_name, os_hooks in result['hooks_os'].items():
|
|
|
|
for hook in os_hooks:
|
|
|
|
UpdateCondition(hook, 'and', 'checkout_' + os_name)
|
|
|
|
hooks.extend(os_hooks)
|
|
|
|
del result['hooks_os']
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
def EvaluateCondition(condition, variables, referenced_variables=None):
|
|
|
|
"""Safely evaluates a boolean condition. Returns the result."""
|
|
|
|
if not referenced_variables:
|
|
|
|
referenced_variables = set()
|
|
|
|
_allowed_names = {'None': None, 'True': True, 'False': False}
|
|
|
|
main_node = ast.parse(condition, mode='eval')
|
|
|
|
if isinstance(main_node, ast.Expression):
|
|
|
|
main_node = main_node.body
|
|
|
|
|
|
|
|
def _convert(node, allow_tuple=False):
|
|
|
|
if isinstance(node, ast.Str):
|
|
|
|
return node.s
|
|
|
|
|
|
|
|
if isinstance(node, ast.Tuple) and allow_tuple:
|
|
|
|
return tuple(map(_convert, node.elts))
|
|
|
|
|
|
|
|
if isinstance(node, ast.Name):
|
|
|
|
if node.id in referenced_variables:
|
|
|
|
raise ValueError('invalid cyclic reference to %r (inside %r)' %
|
|
|
|
(node.id, condition))
|
|
|
|
|
|
|
|
if node.id in _allowed_names:
|
|
|
|
return _allowed_names[node.id]
|
|
|
|
|
|
|
|
if node.id in variables:
|
|
|
|
value = variables[node.id]
|
|
|
|
|
|
|
|
# Allow using "native" types, without wrapping everything in
|
|
|
|
# strings. Note that schema constraints still apply to
|
|
|
|
# variables.
|
|
|
|
if not isinstance(value, str):
|
|
|
|
return value
|
|
|
|
|
|
|
|
# Recursively evaluate the variable reference.
|
|
|
|
return EvaluateCondition(variables[node.id], variables,
|
|
|
|
referenced_variables.union([node.id]))
|
|
|
|
|
|
|
|
# Implicitly convert unrecognized names to strings.
|
|
|
|
# If we want to change this, we'll need to explicitly distinguish
|
|
|
|
# between arguments for GN to be passed verbatim, and ones to
|
|
|
|
# be evaluated.
|
|
|
|
return node.id
|
|
|
|
|
|
|
|
if not sys.version_info[:2] < (3, 4) and isinstance(
|
|
|
|
node, ast.NameConstant): # Since Python 3.4
|
|
|
|
return node.value
|
|
|
|
|
|
|
|
if isinstance(node, ast.BoolOp) and isinstance(node.op, ast.Or):
|
|
|
|
bool_values = []
|
|
|
|
for value in node.values:
|
|
|
|
bool_values.append(_convert(value))
|
|
|
|
if not isinstance(bool_values[-1], bool):
|
|
|
|
raise ValueError('invalid "or" operand %r (inside %r)' %
|
|
|
|
(bool_values[-1], condition))
|
|
|
|
return any(bool_values)
|
|
|
|
|
|
|
|
if isinstance(node, ast.BoolOp) and isinstance(node.op, ast.And):
|
|
|
|
bool_values = []
|
|
|
|
for value in node.values:
|
|
|
|
bool_values.append(_convert(value))
|
|
|
|
if not isinstance(bool_values[-1], bool):
|
|
|
|
raise ValueError('invalid "and" operand %r (inside %r)' %
|
|
|
|
(bool_values[-1], condition))
|
|
|
|
return all(bool_values)
|
|
|
|
|
|
|
|
if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.Not):
|
|
|
|
value = _convert(node.operand)
|
|
|
|
if not isinstance(value, bool):
|
|
|
|
raise ValueError('invalid "not" operand %r (inside %r)' %
|
|
|
|
(value, condition))
|
|
|
|
return not value
|
|
|
|
|
|
|
|
if isinstance(node, ast.Compare):
|
|
|
|
if len(node.ops) != 1:
|
|
|
|
raise ValueError(
|
|
|
|
'invalid compare: exactly 1 operator required (inside %r)' %
|
|
|
|
(condition))
|
|
|
|
if len(node.comparators) != 1:
|
|
|
|
raise ValueError(
|
|
|
|
'invalid compare: exactly 1 comparator required (inside %r)'
|
|
|
|
% (condition))
|
|
|
|
|
|
|
|
left = _convert(node.left)
|
|
|
|
right = _convert(node.comparators[0],
|
|
|
|
allow_tuple=isinstance(node.ops[0], ast.In))
|
|
|
|
|
|
|
|
if isinstance(node.ops[0], ast.Eq):
|
|
|
|
return left == right
|
|
|
|
if isinstance(node.ops[0], ast.NotEq):
|
|
|
|
return left != right
|
|
|
|
if isinstance(node.ops[0], ast.In):
|
|
|
|
return left in right
|
|
|
|
|
|
|
|
raise ValueError('unexpected operator: %s %s (inside %r)' %
|
|
|
|
(node.ops[0], ast.dump(node), condition))
|
|
|
|
|
|
|
|
raise ValueError('unexpected AST node: %s %s (inside %r)' %
|
|
|
|
(node, ast.dump(node), condition))
|
|
|
|
|
|
|
|
return _convert(main_node)
|
|
|
|
|
|
|
|
|
|
|
|
def RenderDEPSFile(gclient_dict):
|
|
|
|
contents = sorted(gclient_dict.tokens.values(), key=lambda token: token[2])
|
|
|
|
return tokenize.untokenize(contents)
|
|
|
|
|
|
|
|
|
|
|
|
def _UpdateAstString(tokens, node, value):
|
|
|
|
if isinstance(node, ast.Call):
|
|
|
|
node = node.args[0]
|
|
|
|
position = node.lineno, node.col_offset
|
|
|
|
quote_char = ''
|
|
|
|
if isinstance(node, ast.Str):
|
|
|
|
quote_char = tokens[position][1][0]
|
|
|
|
value = value.encode('unicode_escape').decode('utf-8')
|
|
|
|
tokens[position][1] = quote_char + value + quote_char
|
|
|
|
node.s = value
|
|
|
|
|
|
|
|
|
|
|
|
def _ShiftLinesInTokens(tokens, delta, start):
|
|
|
|
new_tokens = {}
|
|
|
|
for token in tokens.values():
|
|
|
|
if token[2][0] >= start:
|
|
|
|
token[2] = token[2][0] + delta, token[2][1]
|
|
|
|
token[3] = token[3][0] + delta, token[3][1]
|
|
|
|
new_tokens[token[2]] = token
|
|
|
|
return new_tokens
|
|
|
|
|
|
|
|
|
|
|
|
def AddVar(gclient_dict, var_name, value):
|
|
|
|
if not isinstance(gclient_dict, _NodeDict) or gclient_dict.tokens is None:
|
|
|
|
raise ValueError(
|
|
|
|
"Can't use SetVar for the given gclient dict. It contains no "
|
|
|
|
"formatting information.")
|
|
|
|
|
|
|
|
if 'vars' not in gclient_dict:
|
|
|
|
raise KeyError("vars dict is not defined.")
|
|
|
|
|
|
|
|
if var_name in gclient_dict['vars']:
|
|
|
|
raise ValueError(
|
|
|
|
"%s has already been declared in the vars dict. Consider using SetVar "
|
|
|
|
"instead." % var_name)
|
|
|
|
|
|
|
|
if not gclient_dict['vars']:
|
|
|
|
raise ValueError('vars dict is empty. This is not yet supported.')
|
|
|
|
|
|
|
|
# We will attempt to add the var right after 'vars = {'.
|
|
|
|
node = gclient_dict.GetNode('vars')
|
|
|
|
if node is None:
|
|
|
|
raise ValueError("The vars dict has no formatting information." %
|
|
|
|
var_name)
|
|
|
|
line = node.lineno + 1
|
|
|
|
|
|
|
|
# We will try to match the new var's indentation to the next variable.
|
|
|
|
col = node.keys[0].col_offset
|
|
|
|
|
|
|
|
# We use a minimal Python dictionary, so that ast can parse it.
|
|
|
|
var_content = '{\n%s"%s": "%s",\n}\n' % (' ' * col, var_name, value)
|
|
|
|
var_ast = ast.parse(var_content).body[0].value
|
|
|
|
|
|
|
|
# Set the ast nodes for the key and value.
|
|
|
|
vars_node = gclient_dict.GetNode('vars')
|
|
|
|
|
|
|
|
var_name_node = var_ast.keys[0]
|
|
|
|
var_name_node.lineno += line - 2
|
|
|
|
vars_node.keys.insert(0, var_name_node)
|
|
|
|
|
|
|
|
value_node = var_ast.values[0]
|
|
|
|
value_node.lineno += line - 2
|
|
|
|
vars_node.values.insert(0, value_node)
|
|
|
|
|
|
|
|
# Update the tokens.
|
|
|
|
var_tokens = list(tokenize.generate_tokens(StringIO(var_content).readline))
|
|
|
|
var_tokens = {
|
|
|
|
token[2]: list(token)
|
|
|
|
# Ignore the tokens corresponding to braces and new lines.
|
|
|
|
for token in var_tokens[2:-3]
|
|
|
|
}
|
|
|
|
|
|
|
|
gclient_dict.tokens = _ShiftLinesInTokens(gclient_dict.tokens, 1, line)
|
|
|
|
gclient_dict.tokens.update(_ShiftLinesInTokens(var_tokens, line - 2, 0))
|
|
|
|
|
|
|
|
|
|
|
|
def SetVar(gclient_dict, var_name, value):
|
|
|
|
if not isinstance(gclient_dict, _NodeDict) or gclient_dict.tokens is None:
|
|
|
|
raise ValueError(
|
|
|
|
"Can't use SetVar for the given gclient dict. It contains no "
|
|
|
|
"formatting information.")
|
|
|
|
tokens = gclient_dict.tokens
|
|
|
|
|
|
|
|
if 'vars' not in gclient_dict:
|
|
|
|
raise KeyError("vars dict is not defined.")
|
|
|
|
|
|
|
|
if var_name not in gclient_dict['vars']:
|
|
|
|
raise ValueError(
|
|
|
|
"%s has not been declared in the vars dict. Consider using AddVar "
|
|
|
|
"instead." % var_name)
|
|
|
|
|
|
|
|
node = gclient_dict['vars'].GetNode(var_name)
|
|
|
|
if node is None:
|
|
|
|
raise ValueError(
|
|
|
|
"The vars entry for %s has no formatting information." % var_name)
|
|
|
|
|
|
|
|
_UpdateAstString(tokens, node, value)
|
|
|
|
gclient_dict['vars'].SetNode(var_name, value, node)
|
|
|
|
|
|
|
|
|
|
|
|
def _GetVarName(node):
|
|
|
|
if isinstance(node, ast.Call):
|
|
|
|
return node.args[0].s
|
|
|
|
|
|
|
|
if node.s.endswith('}'):
|
|
|
|
last_brace = node.s.rfind('{')
|
|
|
|
return node.s[last_brace + 1:-1]
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
Implement setdeps for GCS
The number of revision objects must match the number of current objects
for a given path. The comma separated values for a revision object
must also be in the order of object_name, sha256sum, size_bytes, and
generation.
Example usage:
gclient setdep --revision=src/third_party/js_code_coverage@myobjectname,deadbeef,1223455,9438393984/myobjectname2,deadbefeaef,1223455,9438393984
Resulting diff:
diff --git a/DEPS b/DEPS
index b40bca8cdd89d..c2186185c1db5 100644
--- a/DEPS
+++ b/DEPS
@@ -553,10 +553,10 @@ deps = {
'bucket': 'chromium-nodejs',
'objects': [
{
- 'object_name': 'js_code_coverage/d538975c93eefc7bafd599b50f867e90c1ef17f3',
- 'sha256sum': '646bb00ced0a930b2eb1e4dbcfac18ebbb8f889bb80599e0254d9d6505427914',
- 'size_bytes': 1469185,
- 'generation': 1657780123604338,
+ 'object_name': 'myobjectname',
+ 'sha256sum': 'deadbeef',
+ 'size_bytes': 1223455,
+ 'generation': 9438393984,
},
],
},
Bug: b/324418194
Change-Id: Ibd824f7b51fa88f732c7197e2cc663b58de7479e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/5503101
Commit-Queue: Stephanie Kim <kimstephanie@google.com>
Reviewed-by: Joanna Wang <jojwang@chromium.org>
10 months ago
|
|
|
def SetGCS(gclient_dict, dep_name, new_objects):
|
|
|
|
if not isinstance(gclient_dict, _NodeDict) or gclient_dict.tokens is None:
|
|
|
|
raise ValueError(
|
|
|
|
"Can't use SetGCS for the given gclient dict. It contains no "
|
|
|
|
"formatting information.")
|
|
|
|
tokens = gclient_dict.tokens
|
|
|
|
|
|
|
|
if 'deps' not in gclient_dict or dep_name not in gclient_dict['deps']:
|
|
|
|
raise KeyError("Could not find any dependency called %s." % dep_name)
|
|
|
|
|
|
|
|
node = gclient_dict['deps'][dep_name]
|
|
|
|
objects_node = node.GetNode('objects')
|
|
|
|
if len(objects_node.elts) != len(new_objects):
|
|
|
|
raise ValueError("Number of revision objects must match the current "
|
|
|
|
"number of objects.")
|
|
|
|
|
|
|
|
# Allow only `keys_to_update` to be updated.
|
|
|
|
keys_to_update = ('object_name', 'sha256sum', 'size_bytes', 'generation')
|
Implement setdeps for GCS
The number of revision objects must match the number of current objects
for a given path. The comma separated values for a revision object
must also be in the order of object_name, sha256sum, size_bytes, and
generation.
Example usage:
gclient setdep --revision=src/third_party/js_code_coverage@myobjectname,deadbeef,1223455,9438393984/myobjectname2,deadbefeaef,1223455,9438393984
Resulting diff:
diff --git a/DEPS b/DEPS
index b40bca8cdd89d..c2186185c1db5 100644
--- a/DEPS
+++ b/DEPS
@@ -553,10 +553,10 @@ deps = {
'bucket': 'chromium-nodejs',
'objects': [
{
- 'object_name': 'js_code_coverage/d538975c93eefc7bafd599b50f867e90c1ef17f3',
- 'sha256sum': '646bb00ced0a930b2eb1e4dbcfac18ebbb8f889bb80599e0254d9d6505427914',
- 'size_bytes': 1469185,
- 'generation': 1657780123604338,
+ 'object_name': 'myobjectname',
+ 'sha256sum': 'deadbeef',
+ 'size_bytes': 1223455,
+ 'generation': 9438393984,
},
],
},
Bug: b/324418194
Change-Id: Ibd824f7b51fa88f732c7197e2cc663b58de7479e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/5503101
Commit-Queue: Stephanie Kim <kimstephanie@google.com>
Reviewed-by: Joanna Wang <jojwang@chromium.org>
10 months ago
|
|
|
for index, object_node in enumerate(objects_node.elts):
|
|
|
|
for key, value in zip(object_node.keys, object_node.values):
|
|
|
|
if key.s not in keys_to_update:
|
|
|
|
continue
|
Implement setdeps for GCS
The number of revision objects must match the number of current objects
for a given path. The comma separated values for a revision object
must also be in the order of object_name, sha256sum, size_bytes, and
generation.
Example usage:
gclient setdep --revision=src/third_party/js_code_coverage@myobjectname,deadbeef,1223455,9438393984/myobjectname2,deadbefeaef,1223455,9438393984
Resulting diff:
diff --git a/DEPS b/DEPS
index b40bca8cdd89d..c2186185c1db5 100644
--- a/DEPS
+++ b/DEPS
@@ -553,10 +553,10 @@ deps = {
'bucket': 'chromium-nodejs',
'objects': [
{
- 'object_name': 'js_code_coverage/d538975c93eefc7bafd599b50f867e90c1ef17f3',
- 'sha256sum': '646bb00ced0a930b2eb1e4dbcfac18ebbb8f889bb80599e0254d9d6505427914',
- 'size_bytes': 1469185,
- 'generation': 1657780123604338,
+ 'object_name': 'myobjectname',
+ 'sha256sum': 'deadbeef',
+ 'size_bytes': 1223455,
+ 'generation': 9438393984,
},
],
},
Bug: b/324418194
Change-Id: Ibd824f7b51fa88f732c7197e2cc663b58de7479e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/5503101
Commit-Queue: Stephanie Kim <kimstephanie@google.com>
Reviewed-by: Joanna Wang <jojwang@chromium.org>
10 months ago
|
|
|
_UpdateAstString(tokens, value, new_objects[index][key.s])
|
|
|
|
|
|
|
|
node.SetNode('objects', new_objects, objects_node)
|
|
|
|
|
|
|
|
|
|
|
|
def SetCIPD(gclient_dict, dep_name, package_name, new_version):
|
|
|
|
if not isinstance(gclient_dict, _NodeDict) or gclient_dict.tokens is None:
|
|
|
|
raise ValueError(
|
|
|
|
"Can't use SetCIPD for the given gclient dict. It contains no "
|
|
|
|
"formatting information.")
|
|
|
|
tokens = gclient_dict.tokens
|
|
|
|
|
|
|
|
if 'deps' not in gclient_dict or dep_name not in gclient_dict['deps']:
|
|
|
|
raise KeyError("Could not find any dependency called %s." % dep_name)
|
|
|
|
|
|
|
|
# Find the package with the given name
|
|
|
|
packages = [
|
|
|
|
package for package in gclient_dict['deps'][dep_name]['packages']
|
|
|
|
if package['package'] == package_name
|
|
|
|
]
|
|
|
|
if len(packages) != 1:
|
|
|
|
raise ValueError(
|
|
|
|
"There must be exactly one package with the given name (%s), "
|
|
|
|
"%s were found." % (package_name, len(packages)))
|
|
|
|
|
|
|
|
# TODO(ehmaldonado): Support Var in package's version.
|
|
|
|
node = packages[0].GetNode('version')
|
|
|
|
if node is None:
|
|
|
|
raise ValueError(
|
|
|
|
"The deps entry for %s:%s has no formatting information." %
|
|
|
|
(dep_name, package_name))
|
|
|
|
|
|
|
|
if not isinstance(node, ast.Call) and not isinstance(node, ast.Str):
|
|
|
|
raise ValueError(
|
|
|
|
"Unsupported dependency revision format. Please file a bug to the "
|
|
|
|
"Infra>SDK component in crbug.com")
|
|
|
|
|
|
|
|
var_name = _GetVarName(node)
|
|
|
|
if var_name is not None:
|
|
|
|
SetVar(gclient_dict, var_name, new_version)
|
|
|
|
else:
|
|
|
|
_UpdateAstString(tokens, node, new_version)
|
|
|
|
packages[0].SetNode('version', new_version, node)
|
|
|
|
|
|
|
|
|
|
|
|
def SetRevision(gclient_dict, dep_name, new_revision):
|
|
|
|
def _UpdateRevision(dep_dict, dep_key, new_revision):
|
|
|
|
dep_node = dep_dict.GetNode(dep_key)
|
|
|
|
if dep_node is None:
|
|
|
|
raise ValueError(
|
|
|
|
"The deps entry for %s has no formatting information." %
|
|
|
|
dep_name)
|
|
|
|
|
|
|
|
node = dep_node
|
|
|
|
if isinstance(node, ast.BinOp):
|
|
|
|
node = node.right
|
|
|
|
|
|
|
|
if isinstance(node, ast.Str):
|
|
|
|
token = _gclient_eval(tokens[node.lineno, node.col_offset][1])
|
|
|
|
if token != node.s:
|
|
|
|
raise ValueError(
|
|
|
|
'Can\'t update value for %s. Multiline strings and implicitly '
|
|
|
|
'concatenated strings are not supported.\n'
|
|
|
|
'Consider reformatting the DEPS file.' % dep_key)
|
|
|
|
|
|
|
|
if not isinstance(node, ast.Call) and not isinstance(node, ast.Str):
|
|
|
|
raise ValueError(
|
|
|
|
"Unsupported dependency revision format. Please file a bug to the "
|
|
|
|
"Infra>SDK component in crbug.com")
|
|
|
|
|
|
|
|
var_name = _GetVarName(node)
|
|
|
|
if var_name is not None:
|
|
|
|
SetVar(gclient_dict, var_name, new_revision)
|
|
|
|
else:
|
|
|
|
if '@' in node.s:
|
|
|
|
# '@' is part of the last string, which we want to modify.
|
|
|
|
# Discard whatever was after the '@' and put the new revision in
|
|
|
|
# its place.
|
|
|
|
new_revision = node.s.split('@')[0] + '@' + new_revision
|
|
|
|
elif '@' not in dep_dict[dep_key]:
|
|
|
|
# '@' is not part of the URL at all. This mean the dependency is
|
|
|
|
# unpinned and we should pin it.
|
|
|
|
new_revision = node.s + '@' + new_revision
|
|
|
|
_UpdateAstString(tokens, node, new_revision)
|
|
|
|
dep_dict.SetNode(dep_key, new_revision, node)
|
|
|
|
|
|
|
|
if not isinstance(gclient_dict, _NodeDict) or gclient_dict.tokens is None:
|
|
|
|
raise ValueError(
|
|
|
|
"Can't use SetRevision for the given gclient dict. It contains no "
|
|
|
|
"formatting information.")
|
|
|
|
tokens = gclient_dict.tokens
|
|
|
|
|
|
|
|
if 'deps' not in gclient_dict or dep_name not in gclient_dict['deps']:
|
|
|
|
raise KeyError("Could not find any dependency called %s." % dep_name)
|
|
|
|
|
|
|
|
if isinstance(gclient_dict['deps'][dep_name], _NodeDict):
|
|
|
|
_UpdateRevision(gclient_dict['deps'][dep_name], 'url', new_revision)
|
|
|
|
else:
|
|
|
|
_UpdateRevision(gclient_dict['deps'], dep_name, new_revision)
|
|
|
|
|
|
|
|
|
|
|
|
def GetVar(gclient_dict, var_name):
|
|
|
|
if 'vars' not in gclient_dict or var_name not in gclient_dict['vars']:
|
|
|
|
raise KeyError("Could not find any variable called %s." % var_name)
|
|
|
|
|
|
|
|
val = gclient_dict['vars'][var_name]
|
|
|
|
if isinstance(val, ConstantString):
|
|
|
|
return val.value
|
|
|
|
return val
|
|
|
|
|
|
|
|
|
|
|
|
def GetCIPD(gclient_dict, dep_name, package_name):
|
|
|
|
if 'deps' not in gclient_dict or dep_name not in gclient_dict['deps']:
|
|
|
|
raise KeyError("Could not find any dependency called %s." % dep_name)
|
|
|
|
|
|
|
|
# Find the package with the given name
|
|
|
|
packages = [
|
|
|
|
package for package in gclient_dict['deps'][dep_name]['packages']
|
|
|
|
if package['package'] == package_name
|
|
|
|
]
|
|
|
|
if len(packages) != 1:
|
|
|
|
raise ValueError(
|
|
|
|
"There must be exactly one package with the given name (%s), "
|
|
|
|
"%s were found." % (package_name, len(packages)))
|
|
|
|
|
|
|
|
return packages[0]['version']
|
|
|
|
|
|
|
|
|
|
|
|
def GetRevision(gclient_dict, dep_name):
|
|
|
|
if 'deps' not in gclient_dict or dep_name not in gclient_dict['deps']:
|
|
|
|
suggestions = []
|
|
|
|
if 'deps' in gclient_dict:
|
|
|
|
for key in gclient_dict['deps']:
|
|
|
|
if dep_name in key:
|
|
|
|
suggestions.append(key)
|
|
|
|
if suggestions:
|
|
|
|
raise KeyError(
|
|
|
|
"Could not find any dependency called %s. Did you mean %s" %
|
|
|
|
(dep_name, ' or '.join(suggestions)))
|
|
|
|
raise KeyError("Could not find any dependency called %s." % dep_name)
|
|
|
|
|
|
|
|
dep = gclient_dict['deps'][dep_name]
|
|
|
|
if dep is None:
|
|
|
|
return None
|
|
|
|
|
|
|
|
if isinstance(dep, str):
|
|
|
|
_, _, revision = dep.partition('@')
|
|
|
|
return revision or None
|
|
|
|
|
|
|
|
if isinstance(dep, collections.abc.Mapping) and 'url' in dep:
|
|
|
|
_, _, revision = dep['url'].partition('@')
|
|
|
|
return revision or None
|
|
|
|
|
|
|
|
if isinstance(gclient_dict, _NodeDict) and 'objects' in dep:
|
|
|
|
return dep['objects']
|
|
|
|
|
|
|
|
raise ValueError('%s is not a valid git or gcs dependency.' % dep_name)
|