xref: /OK3568_Linux_fs/yocto/scripts/install-buildtools (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun#!/usr/bin/env python3
2*4882a593Smuzhiyun
3*4882a593Smuzhiyun# Buildtools and buildtools extended installer helper script
4*4882a593Smuzhiyun#
5*4882a593Smuzhiyun# Copyright (C) 2017-2020 Intel Corporation
6*4882a593Smuzhiyun#
7*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only
8*4882a593Smuzhiyun#
9*4882a593Smuzhiyun#  NOTE: --with-extended-buildtools is on by default
10*4882a593Smuzhiyun#
11*4882a593Smuzhiyun#  Example usage (extended buildtools from milestone):
12*4882a593Smuzhiyun#    (1) using --url and --filename
13*4882a593Smuzhiyun#        $ install-buildtools \
14*4882a593Smuzhiyun#          --url http://downloads.yoctoproject.org/releases/yocto/milestones/yocto-3.1_M3/buildtools \
15*4882a593Smuzhiyun#          --filename x86_64-buildtools-extended-nativesdk-standalone-3.0+snapshot-20200315.sh
16*4882a593Smuzhiyun#    (2) using --base-url, --release, --installer-version and --build-date
17*4882a593Smuzhiyun#        $ install-buildtools \
18*4882a593Smuzhiyun#          --base-url http://downloads.yoctoproject.org/releases/yocto \
19*4882a593Smuzhiyun#          --release yocto-3.1_M3 \
20*4882a593Smuzhiyun#          --installer-version 3.0+snapshot
21*4882a593Smuzhiyun#          --build-date 202000315
22*4882a593Smuzhiyun#
23*4882a593Smuzhiyun#  Example usage (standard buildtools from release):
24*4882a593Smuzhiyun#    (3) using --url and --filename
25*4882a593Smuzhiyun#        $ install-buildtools --without-extended-buildtools \
26*4882a593Smuzhiyun#          --url http://downloads.yoctoproject.org/releases/yocto/yocto-3.0.2/buildtools \
27*4882a593Smuzhiyun#          --filename x86_64-buildtools-nativesdk-standalone-3.0.2.sh
28*4882a593Smuzhiyun#    (4) using --base-url, --release and --installer-version
29*4882a593Smuzhiyun#        $ install-buildtools --without-extended-buildtools \
30*4882a593Smuzhiyun#          --base-url http://downloads.yoctoproject.org/releases/yocto \
31*4882a593Smuzhiyun#          --release yocto-3.0.2 \
32*4882a593Smuzhiyun#          --installer-version 3.0.2
33*4882a593Smuzhiyun#
34*4882a593Smuzhiyun
35*4882a593Smuzhiyunimport argparse
36*4882a593Smuzhiyunimport logging
37*4882a593Smuzhiyunimport os
38*4882a593Smuzhiyunimport platform
39*4882a593Smuzhiyunimport re
40*4882a593Smuzhiyunimport shutil
41*4882a593Smuzhiyunimport shlex
42*4882a593Smuzhiyunimport stat
43*4882a593Smuzhiyunimport subprocess
44*4882a593Smuzhiyunimport sys
45*4882a593Smuzhiyunimport tempfile
46*4882a593Smuzhiyunfrom urllib.parse import quote
47*4882a593Smuzhiyun
48*4882a593Smuzhiyunscripts_path = os.path.dirname(os.path.realpath(__file__))
49*4882a593Smuzhiyunlib_path = scripts_path + '/lib'
50*4882a593Smuzhiyunsys.path = sys.path + [lib_path]
51*4882a593Smuzhiyunimport scriptutils
52*4882a593Smuzhiyunimport scriptpath
53*4882a593Smuzhiyun
54*4882a593Smuzhiyun
55*4882a593SmuzhiyunPROGNAME = 'install-buildtools'
56*4882a593Smuzhiyunlogger = scriptutils.logger_create(PROGNAME, stream=sys.stdout)
57*4882a593Smuzhiyun
58*4882a593SmuzhiyunDEFAULT_INSTALL_DIR = os.path.join(os.path.split(scripts_path)[0],'buildtools')
59*4882a593SmuzhiyunDEFAULT_BASE_URL = 'http://downloads.yoctoproject.org/releases/yocto'
60*4882a593SmuzhiyunDEFAULT_RELEASE = 'yocto-3.4'
61*4882a593SmuzhiyunDEFAULT_INSTALLER_VERSION = '3.4'
62*4882a593SmuzhiyunDEFAULT_BUILDDATE = '202110XX'
63*4882a593Smuzhiyun
64*4882a593Smuzhiyun# Python version sanity check
65*4882a593Smuzhiyunif not (sys.version_info.major == 3 and sys.version_info.minor >= 4):
66*4882a593Smuzhiyun    logger.error("This script requires Python 3.4 or greater")
67*4882a593Smuzhiyun    logger.error("You have Python %s.%s" %
68*4882a593Smuzhiyun	  (sys.version_info.major, sys.version_info.minor))
69*4882a593Smuzhiyun    sys.exit(1)
70*4882a593Smuzhiyun
71*4882a593Smuzhiyun# The following three functions are copied directly from
72*4882a593Smuzhiyun# bitbake/lib/bb/utils.py, in order to allow this script
73*4882a593Smuzhiyun# to run on versions of python earlier than what bitbake
74*4882a593Smuzhiyun# supports (e.g. less than Python 3.5 for YP 3.1 release)
75*4882a593Smuzhiyun
76*4882a593Smuzhiyundef _hasher(method, filename):
77*4882a593Smuzhiyun    import mmap
78*4882a593Smuzhiyun
79*4882a593Smuzhiyun    with open(filename, "rb") as f:
80*4882a593Smuzhiyun        try:
81*4882a593Smuzhiyun            with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:
82*4882a593Smuzhiyun                for chunk in iter(lambda: mm.read(8192), b''):
83*4882a593Smuzhiyun                    method.update(chunk)
84*4882a593Smuzhiyun        except ValueError:
85*4882a593Smuzhiyun            # You can't mmap() an empty file so silence this exception
86*4882a593Smuzhiyun            pass
87*4882a593Smuzhiyun    return method.hexdigest()
88*4882a593Smuzhiyun
89*4882a593Smuzhiyun
90*4882a593Smuzhiyundef md5_file(filename):
91*4882a593Smuzhiyun    """
92*4882a593Smuzhiyun    Return the hex string representation of the MD5 checksum of filename.
93*4882a593Smuzhiyun    """
94*4882a593Smuzhiyun    import hashlib
95*4882a593Smuzhiyun    return _hasher(hashlib.md5(), filename)
96*4882a593Smuzhiyun
97*4882a593Smuzhiyundef sha256_file(filename):
98*4882a593Smuzhiyun    """
99*4882a593Smuzhiyun    Return the hex string representation of the 256-bit SHA checksum of
100*4882a593Smuzhiyun    filename.
101*4882a593Smuzhiyun    """
102*4882a593Smuzhiyun    import hashlib
103*4882a593Smuzhiyun    return _hasher(hashlib.sha256(), filename)
104*4882a593Smuzhiyun
105*4882a593Smuzhiyun
106*4882a593Smuzhiyundef main():
107*4882a593Smuzhiyun    global DEFAULT_INSTALL_DIR
108*4882a593Smuzhiyun    global DEFAULT_BASE_URL
109*4882a593Smuzhiyun    global DEFAULT_RELEASE
110*4882a593Smuzhiyun    global DEFAULT_INSTALLER_VERSION
111*4882a593Smuzhiyun    global DEFAULT_BUILDDATE
112*4882a593Smuzhiyun    filename = ""
113*4882a593Smuzhiyun    release = ""
114*4882a593Smuzhiyun    buildtools_url = ""
115*4882a593Smuzhiyun    install_dir = ""
116*4882a593Smuzhiyun    arch = platform.machine()
117*4882a593Smuzhiyun
118*4882a593Smuzhiyun    parser = argparse.ArgumentParser(
119*4882a593Smuzhiyun        description="Buildtools installation helper",
120*4882a593Smuzhiyun        add_help=False)
121*4882a593Smuzhiyun    parser.add_argument('-u', '--url',
122*4882a593Smuzhiyun                        help='URL from where to fetch buildtools SDK installer, not '
123*4882a593Smuzhiyun                             'including filename (optional)\n'
124*4882a593Smuzhiyun                             'Requires --filename.',
125*4882a593Smuzhiyun                        action='store')
126*4882a593Smuzhiyun    parser.add_argument('-f', '--filename',
127*4882a593Smuzhiyun                        help='filename for the buildtools SDK installer to be installed '
128*4882a593Smuzhiyun                             '(optional)\nRequires --url',
129*4882a593Smuzhiyun                        action='store')
130*4882a593Smuzhiyun    parser.add_argument('-d', '--directory',
131*4882a593Smuzhiyun                        default=DEFAULT_INSTALL_DIR,
132*4882a593Smuzhiyun                        help='directory where buildtools SDK will be installed (optional)',
133*4882a593Smuzhiyun                        action='store')
134*4882a593Smuzhiyun    parser.add_argument('-r', '--release',
135*4882a593Smuzhiyun                        default=DEFAULT_RELEASE,
136*4882a593Smuzhiyun                        help='Yocto Project release string for SDK which will be '
137*4882a593Smuzhiyun                             'installed (optional)',
138*4882a593Smuzhiyun                        action='store')
139*4882a593Smuzhiyun    parser.add_argument('-V', '--installer-version',
140*4882a593Smuzhiyun                        default=DEFAULT_INSTALLER_VERSION,
141*4882a593Smuzhiyun                        help='version string for the SDK to be installed (optional)',
142*4882a593Smuzhiyun                        action='store')
143*4882a593Smuzhiyun    parser.add_argument('-b', '--base-url',
144*4882a593Smuzhiyun                        default=DEFAULT_BASE_URL,
145*4882a593Smuzhiyun                        help='base URL from which to fetch SDK (optional)', action='store')
146*4882a593Smuzhiyun    parser.add_argument('-t', '--build-date',
147*4882a593Smuzhiyun                        default=DEFAULT_BUILDDATE,
148*4882a593Smuzhiyun                        help='Build date of pre-release SDK (optional)', action='store')
149*4882a593Smuzhiyun    group = parser.add_mutually_exclusive_group()
150*4882a593Smuzhiyun    group.add_argument('--with-extended-buildtools', action='store_true',
151*4882a593Smuzhiyun                       dest='with_extended_buildtools',
152*4882a593Smuzhiyun                       default=True,
153*4882a593Smuzhiyun                       help='enable extended buildtools tarball (on by default)')
154*4882a593Smuzhiyun    group.add_argument('--without-extended-buildtools', action='store_false',
155*4882a593Smuzhiyun                       dest='with_extended_buildtools',
156*4882a593Smuzhiyun                       help='disable extended buildtools (traditional buildtools tarball)')
157*4882a593Smuzhiyun    group = parser.add_mutually_exclusive_group()
158*4882a593Smuzhiyun    group.add_argument('-c', '--check', help='enable checksum validation',
159*4882a593Smuzhiyun                        default=True, action='store_true')
160*4882a593Smuzhiyun    group.add_argument('-n', '--no-check', help='disable checksum validation',
161*4882a593Smuzhiyun                        dest="check", action='store_false')
162*4882a593Smuzhiyun    parser.add_argument('-D', '--debug', help='enable debug output',
163*4882a593Smuzhiyun                        action='store_true')
164*4882a593Smuzhiyun    parser.add_argument('-q', '--quiet', help='print only errors',
165*4882a593Smuzhiyun                        action='store_true')
166*4882a593Smuzhiyun
167*4882a593Smuzhiyun    parser.add_argument('-h', '--help', action='help',
168*4882a593Smuzhiyun                        default=argparse.SUPPRESS,
169*4882a593Smuzhiyun                        help='show this help message and exit')
170*4882a593Smuzhiyun
171*4882a593Smuzhiyun    args = parser.parse_args()
172*4882a593Smuzhiyun
173*4882a593Smuzhiyun    if args.debug:
174*4882a593Smuzhiyun        logger.setLevel(logging.DEBUG)
175*4882a593Smuzhiyun    elif args.quiet:
176*4882a593Smuzhiyun        logger.setLevel(logging.ERROR)
177*4882a593Smuzhiyun
178*4882a593Smuzhiyun    if args.url and args.filename:
179*4882a593Smuzhiyun        logger.debug("--url and --filename detected. Ignoring --base-url "
180*4882a593Smuzhiyun                     "--release --installer-version  arguments.")
181*4882a593Smuzhiyun        filename = args.filename
182*4882a593Smuzhiyun        buildtools_url = "%s/%s" % (args.url, filename)
183*4882a593Smuzhiyun    else:
184*4882a593Smuzhiyun        if args.base_url:
185*4882a593Smuzhiyun            base_url = args.base_url
186*4882a593Smuzhiyun        else:
187*4882a593Smuzhiyun            base_url = DEFAULT_BASE_URL
188*4882a593Smuzhiyun        if args.release:
189*4882a593Smuzhiyun            # check if this is a pre-release "milestone" SDK
190*4882a593Smuzhiyun            m = re.search(r"^(?P<distro>[a-zA-Z\-]+)(?P<version>[0-9.]+)(?P<milestone>_M[1-9])$",
191*4882a593Smuzhiyun                          args.release)
192*4882a593Smuzhiyun            logger.debug("milestone regex: %s" % m)
193*4882a593Smuzhiyun            if m and m.group('milestone'):
194*4882a593Smuzhiyun                logger.debug("release[distro]: %s" % m.group('distro'))
195*4882a593Smuzhiyun                logger.debug("release[version]: %s" % m.group('version'))
196*4882a593Smuzhiyun                logger.debug("release[milestone]: %s" % m.group('milestone'))
197*4882a593Smuzhiyun                if not args.build_date:
198*4882a593Smuzhiyun                    logger.error("Milestone installers require --build-date")
199*4882a593Smuzhiyun                else:
200*4882a593Smuzhiyun                    if args.with_extended_buildtools:
201*4882a593Smuzhiyun                        filename = "%s-buildtools-extended-nativesdk-standalone-%s-%s.sh" % (
202*4882a593Smuzhiyun                            arch, args.installer_version, args.build_date)
203*4882a593Smuzhiyun                    else:
204*4882a593Smuzhiyun                        filename = "%s-buildtools-nativesdk-standalone-%s-%s.sh" % (
205*4882a593Smuzhiyun                            arch, args.installer_version, args.build_date)
206*4882a593Smuzhiyun                    safe_filename = quote(filename)
207*4882a593Smuzhiyun                    buildtools_url = "%s/milestones/%s/buildtools/%s" % (base_url, args.release, safe_filename)
208*4882a593Smuzhiyun            # regular release SDK
209*4882a593Smuzhiyun            else:
210*4882a593Smuzhiyun                if args.with_extended_buildtools:
211*4882a593Smuzhiyun                    filename = "%s-buildtools-extended-nativesdk-standalone-%s.sh" % (arch, args.installer_version)
212*4882a593Smuzhiyun                else:
213*4882a593Smuzhiyun                    filename = "%s-buildtools-nativesdk-standalone-%s.sh" % (arch, args.installer_version)
214*4882a593Smuzhiyun                safe_filename = quote(filename)
215*4882a593Smuzhiyun                buildtools_url = "%s/%s/buildtools/%s" % (base_url, args.release, safe_filename)
216*4882a593Smuzhiyun
217*4882a593Smuzhiyun    tmpsdk_dir = tempfile.mkdtemp()
218*4882a593Smuzhiyun    try:
219*4882a593Smuzhiyun        # Fetch installer
220*4882a593Smuzhiyun        logger.info("Fetching buildtools installer")
221*4882a593Smuzhiyun        tmpbuildtools = os.path.join(tmpsdk_dir, filename)
222*4882a593Smuzhiyun        ret = subprocess.call("wget -q -O %s %s" %
223*4882a593Smuzhiyun                              (tmpbuildtools, buildtools_url), shell=True)
224*4882a593Smuzhiyun        if ret != 0:
225*4882a593Smuzhiyun            logger.error("Could not download file from %s" % buildtools_url)
226*4882a593Smuzhiyun            return ret
227*4882a593Smuzhiyun
228*4882a593Smuzhiyun        # Verify checksum
229*4882a593Smuzhiyun        if args.check:
230*4882a593Smuzhiyun            logger.info("Fetching buildtools installer checksum")
231*4882a593Smuzhiyun            checksum_type = ""
232*4882a593Smuzhiyun            for checksum_type in ["md5sum", "sha256sum"]:
233*4882a593Smuzhiyun                check_url = "{}.{}".format(buildtools_url, checksum_type)
234*4882a593Smuzhiyun                checksum_filename = "{}.{}".format(filename, checksum_type)
235*4882a593Smuzhiyun                tmpbuildtools_checksum = os.path.join(tmpsdk_dir, checksum_filename)
236*4882a593Smuzhiyun                ret = subprocess.call("wget -q -O %s %s" %
237*4882a593Smuzhiyun                                      (tmpbuildtools_checksum, check_url), shell=True)
238*4882a593Smuzhiyun                if ret == 0:
239*4882a593Smuzhiyun                    break
240*4882a593Smuzhiyun            else:
241*4882a593Smuzhiyun                if ret != 0:
242*4882a593Smuzhiyun                    logger.error("Could not download file from %s" % check_url)
243*4882a593Smuzhiyun                    return ret
244*4882a593Smuzhiyun            regex = re.compile(r"^(?P<checksum>[0-9a-f]+)\s+(?P<path>.*/)?(?P<filename>.*)$")
245*4882a593Smuzhiyun            with open(tmpbuildtools_checksum, 'rb') as f:
246*4882a593Smuzhiyun                original = f.read()
247*4882a593Smuzhiyun                m = re.search(regex, original.decode("utf-8"))
248*4882a593Smuzhiyun                logger.debug("checksum regex match: %s" % m)
249*4882a593Smuzhiyun                logger.debug("checksum: %s" % m.group('checksum'))
250*4882a593Smuzhiyun                logger.debug("path: %s" % m.group('path'))
251*4882a593Smuzhiyun                logger.debug("filename: %s" % m.group('filename'))
252*4882a593Smuzhiyun                if filename != m.group('filename'):
253*4882a593Smuzhiyun                    logger.error("Filename does not match name in checksum")
254*4882a593Smuzhiyun                    return 1
255*4882a593Smuzhiyun                checksum = m.group('checksum')
256*4882a593Smuzhiyun            if checksum_type == "md5sum":
257*4882a593Smuzhiyun                checksum_value = md5_file(tmpbuildtools)
258*4882a593Smuzhiyun            else:
259*4882a593Smuzhiyun                checksum_value = sha256_file(tmpbuildtools)
260*4882a593Smuzhiyun            if checksum == checksum_value:
261*4882a593Smuzhiyun                    logger.info("Checksum success")
262*4882a593Smuzhiyun            else:
263*4882a593Smuzhiyun                logger.error("Checksum %s expected. Actual checksum is %s." %
264*4882a593Smuzhiyun                             (checksum, checksum_value))
265*4882a593Smuzhiyun                return 1
266*4882a593Smuzhiyun
267*4882a593Smuzhiyun        # Make installer executable
268*4882a593Smuzhiyun        logger.info("Making installer executable")
269*4882a593Smuzhiyun        st = os.stat(tmpbuildtools)
270*4882a593Smuzhiyun        os.chmod(tmpbuildtools, st.st_mode | stat.S_IEXEC)
271*4882a593Smuzhiyun        logger.debug(os.stat(tmpbuildtools))
272*4882a593Smuzhiyun        if args.directory:
273*4882a593Smuzhiyun            install_dir = args.directory
274*4882a593Smuzhiyun            ret = subprocess.call("%s -d %s -y" %
275*4882a593Smuzhiyun                                  (tmpbuildtools, install_dir), shell=True)
276*4882a593Smuzhiyun        else:
277*4882a593Smuzhiyun            install_dir = "/opt/poky/%s" % args.installer_version
278*4882a593Smuzhiyun            ret = subprocess.call("%s -y" % tmpbuildtools, shell=True)
279*4882a593Smuzhiyun        if ret != 0:
280*4882a593Smuzhiyun            logger.error("Could not run buildtools installer")
281*4882a593Smuzhiyun            return ret
282*4882a593Smuzhiyun
283*4882a593Smuzhiyun        # Setup the environment
284*4882a593Smuzhiyun        logger.info("Setting up the environment")
285*4882a593Smuzhiyun        regex = re.compile(r'^(?P<export>export )?(?P<env_var>[A-Z_]+)=(?P<env_val>.+)$')
286*4882a593Smuzhiyun        with open("%s/environment-setup-%s-pokysdk-linux" %
287*4882a593Smuzhiyun                  (install_dir, arch), 'rb') as f:
288*4882a593Smuzhiyun            for line in f:
289*4882a593Smuzhiyun                match = regex.search(line.decode('utf-8'))
290*4882a593Smuzhiyun                logger.debug("export regex: %s" % match)
291*4882a593Smuzhiyun                if match:
292*4882a593Smuzhiyun                    env_var = match.group('env_var')
293*4882a593Smuzhiyun                    logger.debug("env_var: %s" % env_var)
294*4882a593Smuzhiyun                    env_val = match.group('env_val')
295*4882a593Smuzhiyun                    logger.debug("env_val: %s" % env_val)
296*4882a593Smuzhiyun                    os.environ[env_var] = env_val
297*4882a593Smuzhiyun
298*4882a593Smuzhiyun        # Test installation
299*4882a593Smuzhiyun        logger.info("Testing installation")
300*4882a593Smuzhiyun        tool = ""
301*4882a593Smuzhiyun        m = re.search("extended", tmpbuildtools)
302*4882a593Smuzhiyun        logger.debug("extended regex: %s" % m)
303*4882a593Smuzhiyun        if args.with_extended_buildtools and not m:
304*4882a593Smuzhiyun            logger.info("Ignoring --with-extended-buildtools as filename "
305*4882a593Smuzhiyun                        "does not contain 'extended'")
306*4882a593Smuzhiyun        if args.with_extended_buildtools and m:
307*4882a593Smuzhiyun            tool = 'gcc'
308*4882a593Smuzhiyun        else:
309*4882a593Smuzhiyun            tool = 'tar'
310*4882a593Smuzhiyun        logger.debug("install_dir: %s" % install_dir)
311*4882a593Smuzhiyun        cmd = shlex.split("/usr/bin/which %s" % tool)
312*4882a593Smuzhiyun        logger.debug("cmd: %s" % cmd)
313*4882a593Smuzhiyun        logger.debug("tool: %s" % tool)
314*4882a593Smuzhiyun        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
315*4882a593Smuzhiyun        output, errors = proc.communicate()
316*4882a593Smuzhiyun        logger.debug("proc.args: %s" % proc.args)
317*4882a593Smuzhiyun        logger.debug("proc.communicate(): output %s" % output)
318*4882a593Smuzhiyun        logger.debug("proc.communicate(): errors %s" % errors)
319*4882a593Smuzhiyun        which_tool = output.decode('utf-8')
320*4882a593Smuzhiyun        logger.debug("which %s: %s" % (tool, which_tool))
321*4882a593Smuzhiyun        ret = proc.returncode
322*4882a593Smuzhiyun        if not which_tool.startswith(install_dir):
323*4882a593Smuzhiyun            logger.error("Something went wrong: %s not found in %s" %
324*4882a593Smuzhiyun                         (tool, install_dir))
325*4882a593Smuzhiyun        if ret != 0:
326*4882a593Smuzhiyun            logger.error("Something went wrong: installation failed")
327*4882a593Smuzhiyun        else:
328*4882a593Smuzhiyun            logger.info("Installation successful. Remember to source the "
329*4882a593Smuzhiyun                        "environment setup script now and in any new session.")
330*4882a593Smuzhiyun        return ret
331*4882a593Smuzhiyun
332*4882a593Smuzhiyun    finally:
333*4882a593Smuzhiyun        # cleanup tmp directory
334*4882a593Smuzhiyun        shutil.rmtree(tmpsdk_dir)
335*4882a593Smuzhiyun
336*4882a593Smuzhiyun
337*4882a593Smuzhiyunif __name__ == '__main__':
338*4882a593Smuzhiyun    try:
339*4882a593Smuzhiyun        ret = main()
340*4882a593Smuzhiyun    except Exception:
341*4882a593Smuzhiyun        ret = 1
342*4882a593Smuzhiyun        import traceback
343*4882a593Smuzhiyun
344*4882a593Smuzhiyun        traceback.print_exc()
345*4882a593Smuzhiyun    sys.exit(ret)
346