• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2#
3# Copyright 2018 - The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16"""Common code used by acloud create methods/classes."""
17
18import collections
19import logging
20import os
21import re
22import shutil
23
24from acloud import errors
25from acloud.internal import constants
26from acloud.internal.lib import android_build_client
27from acloud.internal.lib import auth
28from acloud.internal.lib import utils
29
30
31logger = logging.getLogger(__name__)
32
33# Store the file path to upload to the remote instance.
34ExtraFile = collections.namedtuple("ExtraFile", ["source", "target"])
35
36
37def ParseExtraFilesArgs(files_info, path_separator=","):
38    """Parse extra-files argument.
39
40    e.g.
41    ["local_path,gce_path"]
42    -> ExtraFile(source='local_path', target='gce_path')
43
44    Args:
45        files_info: List of strings to be converted to namedtuple ExtraFile.
46        item_separator: String character to separate file info.
47
48    Returns:
49        A list of namedtuple ExtraFile.
50
51    Raises:
52        error.MalformedDictStringError: If files_info is malformed.
53    """
54    extra_files = []
55    if files_info:
56        for file_info in files_info:
57            if path_separator not in file_info:
58                raise errors.MalformedDictStringError(
59                    "Expecting '%s' in '%s'." % (path_separator, file_info))
60            source, target = file_info.split(path_separator)
61            extra_files.append(ExtraFile(source, target))
62    return extra_files
63
64
65def ParseKeyValuePairArgs(dict_str, item_separator=",", key_value_separator=":"):
66    """Helper function to initialize a dict object from string.
67
68    e.g.
69    cpu:2,dpi:240,resolution:1280x800
70    -> {"cpu":"2", "dpi":"240", "resolution":"1280x800"}
71
72    Args:
73        dict_str: A String to be converted to dict object.
74        item_separator: String character to separate items.
75        key_value_separator: String character to separate key and value.
76
77    Returns:
78        Dict created from key:val pairs in dict_str.
79
80    Raises:
81        error.MalformedDictStringError: If dict_str is malformed.
82    """
83    args_dict = {}
84    if not dict_str:
85        return args_dict
86
87    for item in dict_str.split(item_separator):
88        if key_value_separator not in item:
89            raise errors.MalformedDictStringError(
90                "Expecting ':' in '%s' to make a key-val pair" % item)
91        key, value = item.split(key_value_separator)
92        if not value or not key:
93            raise errors.MalformedDictStringError(
94                "Missing key or value in %s, expecting form of 'a:b'" % item)
95        args_dict[key.strip()] = value.strip()
96
97    return args_dict
98
99
100def GetNonEmptyEnvVars(*variable_names):
101    """Get non-empty environment variables.
102
103    Args:
104        variable_names: Strings, the variable names.
105
106    Returns:
107        List of strings, the variable values that are defined and not empty.
108    """
109    return list(filter(None, (os.environ.get(v) for v in variable_names)))
110
111
112def GetCvdHostPackage(package_path=None):
113    """Get cvd host package path.
114
115    Look for the host package in specified path or $ANDROID_HOST_OUT and dist
116    dir then verify existence and get cvd host package path.
117
118    Args:
119        package_path: String of cvd host package path.
120
121    Return:
122        A string, the path to the host package.
123
124    Raises:
125        errors.GetCvdLocalHostPackageError: Can't find cvd host package.
126    """
127    if package_path:
128        if os.path.exists(package_path):
129            return package_path
130        raise errors.GetCvdLocalHostPackageError(
131            "The cvd host package path (%s) doesn't exist." % package_path)
132    dirs_to_check = GetNonEmptyEnvVars(constants.ENV_ANDROID_SOONG_HOST_OUT,
133                                       constants.ENV_ANDROID_HOST_OUT)
134    dist_dir = utils.GetDistDir()
135    if dist_dir:
136        dirs_to_check.append(dist_dir)
137
138    for path in dirs_to_check:
139        cvd_host_package = os.path.join(path, constants.CVD_HOST_PACKAGE)
140        if os.path.exists(cvd_host_package):
141            logger.debug("cvd host package: %s", cvd_host_package)
142            return cvd_host_package
143    raise errors.GetCvdLocalHostPackageError(
144        "Can't find the cvd host package (Try lunching a cuttlefish target"
145        " like aosp_cf_x86_64_phone-userdebug and running 'm'): \n%s" %
146        '\n'.join(dirs_to_check))
147
148
149def FindLocalImage(path, default_name_pattern, raise_error=True):
150    """Find an image file in the given path.
151
152    Args:
153        path: The path to the file or the parent directory.
154        default_name_pattern: A regex string, the file to look for if the path
155                              is a directory.
156
157    Returns:
158        The absolute path to the image file.
159
160    Raises:
161        errors.GetLocalImageError if this method cannot find exactly one image.
162    """
163    path = os.path.abspath(path)
164    if os.path.isdir(path):
165        names = [name for name in os.listdir(path) if
166                 re.fullmatch(default_name_pattern, name)]
167        if not names:
168            if raise_error:
169                raise errors.GetLocalImageError("No image in %s." % path)
170            return None
171        if len(names) != 1:
172            raise errors.GetLocalImageError("More than one image in %s: %s" %
173                                            (path, " ".join(names)))
174        path = os.path.join(path, names[0])
175    if os.path.isfile(path):
176        return path
177    raise errors.GetLocalImageError("%s is not a file." % path)
178
179
180def DownloadRemoteArtifact(cfg, build_target, build_id, artifact, extract_path,
181                           decompress=False):
182    """Download remote artifact.
183
184    Args:
185        cfg: An AcloudConfig instance.
186        build_target: String, the build target, e.g. cf_x86_phone-userdebug.
187        build_id: String, Build id, e.g. "2263051", "P2804227"
188        artifact: String, zip image or cvd host package artifact.
189        extract_path: String, a path include extracted files.
190        decompress: Boolean, if true decompress the artifact.
191    """
192    build_client = android_build_client.AndroidBuildClient(
193        auth.CreateCredentials(cfg))
194    temp_file = os.path.join(extract_path, artifact)
195    build_client.DownloadArtifact(
196        build_target,
197        build_id,
198        artifact,
199        temp_file)
200    if decompress:
201        utils.Decompress(temp_file, extract_path)
202        try:
203            os.remove(temp_file)
204            logger.debug("Deleted temporary file %s", temp_file)
205        except OSError as e:
206            logger.error("Failed to delete temporary file: %s", str(e))
207
208
209def PrepareLocalInstanceDir(instance_dir, avd_spec):
210    """Create a directory for a local cuttlefish or goldfish instance.
211
212    If avd_spec has the local instance directory, this method creates a
213    symbolic link from instance_dir to the directory. Otherwise, it creates an
214    empty directory at instance_dir.
215
216    Args:
217        instance_dir: The absolute path to the default instance directory.
218        avd_spec: AVDSpec object that provides the instance directory.
219    """
220    if os.path.islink(instance_dir):
221        os.remove(instance_dir)
222    else:
223        shutil.rmtree(instance_dir, ignore_errors=True)
224
225    if avd_spec.local_instance_dir:
226        abs_instance_dir = os.path.abspath(avd_spec.local_instance_dir)
227        if instance_dir != abs_instance_dir:
228            os.symlink(abs_instance_dir, instance_dir)
229            return
230    if not os.path.exists(instance_dir):
231        os.makedirs(instance_dir)
232