说明
我们目前需要使用代理才能访问Chromium开源项目,通过给开发环境配置代理可以顺利完成其源码的下载。但是如果需要编译项目,就得使用Google提供的脚本build/install-build-deps-android.sh和gclient runhooks命令完成依赖工具的下载。
gclient runhooks内部采用gsutil封装了对google的云存储gs://的访问,我使用的代理不能正常访问gs://,于是导致依赖工具包无法完成下载。网上搜索了很多资料,有介绍修改boto,也有说gsutil不能使用代理的,都不能解决问题。本文详细说明了一种绕过gsuitl的方法,成功完成依赖工具包的下载,并完成编译。
本文使用的是ubuntu 14.04版本,通过设置如下环境变量来使用代理访问外网:
export http_proxy=http://{username}:{pass}@{domain}:{port}/ export https_proxy=http://{username}:{pass}@{domain}:{port}/
另外:我之前使用过桌面版ubuntu,可以图形化的配置VPN,之后便可以完全按照Google提供的方法完成下载,暂时没搞清楚VPN跟我这里使用的代理有什么不同之处。
安装depot_tools
该过程很简单,按照官网顺序执行即可,我简单记录下命令,不再赘述。
mkdir chromium cd chromium git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git export PATH="$PATH:/home/tntzlx/chromium/depot_tools"
下载源码
该过程很简单,按照官网顺序执行即可,不再赘述。
tntzlx@ubuntu:~/chromium$ mkdir source tntzlx@ubuntu:~/chromium$ cd source/ tntzlx@ubuntu:~/chromium/source$ fetch --nohooks android Running: gclient root Running: gclient config --spec 'solutions = [ { "url": "https://chromium.googlesource.com/chromium/src.git","managed": False,"name": "src","deps_file": ".DEPS.git","custom_deps": {},},] target_os = ["android"] ' Running: gclient sync --nohooks
经过漫长等待即可完成代码下载
依赖包下载
首先执行如下命令
cd src build/install-build-deps-android.sh
执行到后面会遇到如下错误,但是不影响最后的编译,暂时没有处理
Err http://ppa.launchpad.net trusty/main Sources 404 Not Found Err http://ppa.launchpad.net trusty/main amd64 Packages 404 Not Found Err http://ppa.launchpad.net trusty/main i386 Packages 404 Not Found Fetched 3,446 kB in 13s (246 kB/s) W: Failed to fetch http://ppa.launchpad.net/djcj/mediainfo/ubuntu/dists/trusty/main/source/Sources 404 Not Found W: Failed to fetch http://ppa.launchpad.net/djcj/mediainfo/ubuntu/dists/trusty/main/binary-amd64/Packages 404 Not Found W: Failed to fetch http://ppa.launchpad.net/djcj/mediainfo/ubuntu/dists/trusty/main/binary-i386/Packages 404 Not Found E: Some index files Failed to download. They have been ignored,or old ones used instead.
然后本文的重点来了。执行如下命令:
gclient runhooks
执行一段时间,会成功更新一部分工具,后续遇到错误。
0> Failed to fetch file gs://chromium-android-tools/play-services/10.2.0/31843001b7ce94fbdf71f2a9db76b28548a795fa for /tmp/tmpgymXSg/LICENSE,skipping. [Err: Failure: [Errno 1] _ssl.c:510: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify Failed. ] Traceback (most recent call last): File "src/build/android/play_services/update.py",line 526,in <module> sys.exit(main(sys.argv[1:])) File "src/build/android/play_services/update.py",line 96,in main return args.func(args) File "src/build/android/play_services/update.py",line 191,in Download config.version_number)): File "src/build/android/play_services/update.py",line 381,in _CheckLicenseAgreement with open(expected_license_path) as license_file: IOError: [Errno 2] No such file or directory: '/tmp/tmpgymXSg/LICENSE' Error: Command '/usr/bin/python src/build/android/play_services/update.py download' returned non-zero exit status 1 in /home/tntzlx/chromium/source
可以看到build/android/play_services/update.py会触发将gs://chromium-android-tools/play-services/10.2.0/31843001b7ce94fbdf71f2a9db76b28548a795fa下载到本地/tmp/tmpgymXSg/LICENSE。
这里实际是想要下载一份LICENSE文件,需要用户选择接受还是拒绝。当选择接受后,会继续启动下载。但是这里下载失败。原因是我们没有gs://(Google云存储)的访问权限。
解决方案:将"gs://chromium-android-tools/play-services/10.2.0/31843001b7ce94fbdf71f2a9db76b28548a795fa"替换成"https://storage.googleapis.com/chromium-android-tools/play-services/10.2.0/31843001b7ce94fbdf71f2a9db76b28548a795fa",即通过HTTPS协议进行下载。
修改下载脚本
src/build/android/play_services/update.py
def Download(args): ...... license_sha1 = os.path.join(SHA1_DIRECTORY,LICENSE_FILE_NAME + '.sha1') _DownloadFromBucket(bucket_path,license_sha1,new_license,args.verbose,args.dry_run) ......
update.py首先会对比src/build/android/play_services/google_play_services_library.zip.sha1跟src/third_party/android_tools/sdk/extras/google/m2repository/google_play_services_library.zip.sha1这两个文件是否相等。如果相等,则认为不需要更新play_services库。首次下载代码完成后是没有src/third_party/android_tools/sdk/extras/google/m2repository/google_play_services_library.zip.sha1这个文件的,故这里需要进行后面的下载(这部分逻辑代码未贴出)。
Download方法是通过调用_DownloadFromBucket(bucket_path,args.dry_run)实现下载的。bucket_path是下载url的前缀,license_sha1是下载文件的sha1码文件的本地路径,而目标文件的url就是bucket_path和其sha1码的拼接,new_license是下载的目标地址。
_DownloadFromBucket的实现如下,其调用download_from_google_storage.download_from_google_storage完成下载:
def _DownloadFromBucket(bucket_path,sha1_file,destination,verbose,is_dry_run): '''Downloads the file designated by the provided sha1 from a cloud bucket.''' download_from_google_storage.download_from_google_storage( input_filename=sha1_file,base_url=bucket_path,gsutil=_InitGsutil(is_dry_run),num_threads=1,directory=None,recursive=False,force=False,output=destination,ignore_errors=False,sha1_file=sha1_file,verbose=verbose,auto_platform=True,extract=False)
该方法在depot_tools/download_from_google_storage.py中实现:
def download_from_google_storage( input_filename,base_url,gsutil,num_threads,directory,recursive,force,output,ignore_errors,auto_platform,extract): # Start up all the worker threads. ...... t = threading.Thread( target=_downloader_worker_thread,args=[thread_num,work_queue,stdout_queue,ret_codes,extract]) t.daemon = True t.start() all_threads.append(t) ......
通过将下载任务放到一个线程中实现下载,线程的工作函数是:_downloader_worker_thread。
分析_downloader_worker_thread的实现:
def _downloader_worker_thread(thread_num,q,out_q,extract,delete=True): ...... # Check if file exists. file_url = '%s/%s' % (base_url,input_sha1_sum) (code,_,err) = gsutil.check_call('ls',file_url) if code != 0: if code == 404: out_q.put('%d> File %s for %s does not exist,skipping.' % ( thread_num,file_url,output_filename)) ret_codes.put((1,'File %s for %s does not exist.' % ( file_url,output_filename))) else: # Other error,probably auth related (bad ~/.boto,etc). out_q.put('%d> Failed to fetch file %s for %s,skipping. [Err: %s]' % ( thread_num,output_filename,err)) ret_codes.put((1,'Failed to fetch file %s for %s. [Err: %s]' % ( file_url,err))) continue # Fetch the file. out_q.put('%d> Downloading %s...' % (thread_num,output_filename)) try: if delete: os.remove(output_filename) # Delete the file if it exists already. except OSError: if os.path.exists(output_filename): out_q.put('%d> Warning: deleting %s Failed.' % ( thread_num,output_filename)) code,err = gsutil.check_call('cp',output_filename) if code != 0: out_q.put('%d> %s' % (thread_num,err)) ret_codes.put((code,err)) continue ......
_downloader_worker_thread使用封装类gsutil实现对google云存储的访问。上述代码中file_url代表下载的url,是gs://格式的。该方法首先采用gsutil.check_call('ls',file_url)检查目标文件是否存在,之后采用gsutil.check_call('cp',output_filename)将file_url下载到目标文件output_filename。
上述的方法均要访问gs://,我们的代理目前访问不了这个地址,
如下的报错信息,就是出自gsutil.check_call('ls',file_url)这一句的调用。
0> Failed to fetch file gs://chromium-android-tools/play-services/10.2.0/31843001b7ce94fbdf71f2a9db76b28548a795fa for /tmp/tmpgymXSg/LICENSE,in _CheckLicenseAgreement with open(expected_license_path) as license_file: IOError: [Errno 2] No such file or directory: '/tmp/tmpgymXSg/LICENSE' Error: Command '/usr/bin/python src/build/android/play_services/update.py download' returned non-zero exit status 1 in /home/tntzlx/chromium/source
我们知道这个函数的最终目的就是完成文件的下载,于是就采用HTTPS下载,代替gsutil.check_call。这里我们暂且认为要下载的文件一定存在于服务器上,于是直接去掉检查文件是否存在的调用gsutil.check_call('ls',file_url),单纯把gsutil.check_call('cp',output_filename)替换成HTTPS下载。
实现简单的HTTPS文件下载器,代码如下:
import urllib2 httpsPrefix = "https://storage.googleapis.com/" gsPrefix = "gs://" def download_gs_to_file(url,fileName): download_http_to_file(url.replace(gsPrefix,httpsPrefix),fileName) def download_http_to_file(url,fileName): proxy = urllib2.ProxyHandler({'http': 'http://{username}:{password}@{domain}:{port}/','https': 'http://{username}:{password}@{domain}:{port}/'} ) auth = urllib2.HTTPBasicAuthHandler() opener = urllib2.build_opener(proxy,auth,urllib2.HTTPHandler) urllib2.install_opener(opener) response = urllib2.urlopen(url) CHUNK = 16 * 1024 with open(fileName,'wb') as f: while True: chunk = response.read(CHUNK) if not chunk: break f.write(chunk)
该下载器是通过urllib2模块实现的,注意我们需要使用代理,所以需要给urllib2设置代理服务:
proxy = urllib2.ProxyHandler({'http': 'http://{username}:{password}@{domain}:{port}/','https': 'http://{username}:{password}@{domain}:{port}/'} )
另外,当进行大文件下载时urllib2.urlopen.read方法不能一次读完,所以设置一个CHUNK,每次读取16K的数据,写入到目的文件中。
保存为:download_helper.py
将上述_downloader_worker_thread的代码改为:
def _downloader_worker_thread(thread_num,delete=True): ...... file_url = '%s/%s' % (base_url,input_sha1_sum) # Fetch the file. out_q.put('%d> Downloading %s...' % (thread_num,output_filename)) download_helper.download_gs_to_file(file_url,output_filename) ......
即可完成下载。
注意_downloader_worker_thread的最后
elif sys.platform != 'win32': # On non-Windows platforms,key off of the custom header # "x-goog-Meta-executable". code,out,_ = gsutil.check_call('stat',file_url) if code != 0: out_q.put('%d> %s' % (thread_num,err)) ret_codes.put((code,err)) elif re.search(r'executable:\s*1',out): st = os.stat(output_filename) os.chmod(output_filename,st.st_mode | stat.S_IEXEC)
意思是在非win32平台上面,检查下载的文件如果是可执行文件,就加上可执行权限。这里依赖gsutil.check_call('stat',file_url)的第二个返回值来判断是否是可执行文件,我这里偷个懒,暂时给所有下载的文件全部加上可执行权限则修改如下:
elif sys.platform != 'win32': st = os.stat(output_filename) os.chmod(output_filename,st.st_mode | stat.S_IEXEC)
再次执行gclient runhooks即可完成下载
编译
编译生成ninja文件
gn gen --args='target_os="android"' out/Default
启动正式编译
ninja -C out/Default chrome_public_apk
编译报错信息如下:
Exception in thread "main" java.lang.UnsupportedClassVersionError: com/android/tools/lint/Main : Unsupported major.minor version 52.0 at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:803) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) at java.net.URLClassLoader.defineClass(URLClassLoader.java:442) at java.net.URLClassLoader.access$100(URLClassLoader.java:64) at java.net.URLClassLoader$1.run(URLClassLoader.java:354) at java.net.URLClassLoader$1.run(URLClassLoader.java:348) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:347) at java.lang.ClassLoader.loadClass(ClassLoader.java:425) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308) at java.lang.ClassLoader.loadClass(ClassLoader.java:358) at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:482)
根据"Unsupported major.minor version 52.0"得知,这里需要安装jdk1.8。我的单板是jdk1.7.0版本。
解决方案:
oracle官网下载jdk-8u111-linux-x64.tar.gz,解压到单板,并配置环境变量
export JAVA_HOME=/home/java/jdk1.8.0_111 export PATH=${JAVA_HOME}/bin:$PATH
注意这里不要有下面的环境变量,否则会有报错信息。
export CLASSPATH=${JAVA_HOME}/lib
tntzlx@ubuntu:~$ java -version java version "1.8.0_111" Java(TM) SE Runtime Environment (build 1.8.0_111-b14) Java HotSpot(TM) 64-Bit Server VM (build 25.111-b14,mixed mode)
说明修改成功。
此时需要删除out/Defualt目录然后
gn gen --args='target_os="android"' out/Default ninja -C out/Default chrome_public_apk
即可完成编译。