Upgrade p4a #19
3 changed files with 174 additions and 42 deletions
|
@ -237,7 +237,7 @@ main.py that loads it.''')
|
||||||
# Delete the old assets.
|
# Delete the old assets.
|
||||||
shutil.rmtree(assets_dir, ignore_errors=True)
|
shutil.rmtree(assets_dir, ignore_errors=True)
|
||||||
ensure_dir(assets_dir)
|
ensure_dir(assets_dir)
|
||||||
|
# remove make_python_zip()
|
||||||
# Add extra environment variable file into tar-able directory:
|
# Add extra environment variable file into tar-able directory:
|
||||||
env_vars_tarpath = tempfile.mkdtemp(prefix="p4a-extra-env-")
|
env_vars_tarpath = tempfile.mkdtemp(prefix="p4a-extra-env-")
|
||||||
with open(os.path.join(env_vars_tarpath, "p4a_env_vars.txt"), "w") as f:
|
with open(os.path.join(env_vars_tarpath, "p4a_env_vars.txt"), "w") as f:
|
||||||
|
@ -251,7 +251,7 @@ main.py that loads it.''')
|
||||||
# Package up the private data (public not supported).
|
# Package up the private data (public not supported).
|
||||||
use_setup_py = get_dist_info_for("use_setup_py",
|
use_setup_py = get_dist_info_for("use_setup_py",
|
||||||
error_if_missing=False) is True
|
error_if_missing=False) is True
|
||||||
private_tar_dirs = [env_vars_tarpath]
|
tar_dirs = [env_vars_tarpath]
|
||||||
_temp_dirs_to_clean = []
|
_temp_dirs_to_clean = []
|
||||||
try:
|
try:
|
||||||
if args.private:
|
if args.private:
|
||||||
|
@ -261,13 +261,22 @@ main.py that loads it.''')
|
||||||
):
|
):
|
||||||
print('No setup.py/pyproject.toml used, copying '
|
print('No setup.py/pyproject.toml used, copying '
|
||||||
'full private data into .apk.')
|
'full private data into .apk.')
|
||||||
private_tar_dirs.append(args.private)
|
tar_dirs.append(args.private)
|
||||||
else:
|
else:
|
||||||
print("Copying main.py's ONLY, since other app data is "
|
print("Copying main.py's ONLY, since other app data is "
|
||||||
"expected in site-packages.")
|
"expected in site-packages.")
|
||||||
main_py_only_dir = tempfile.mkdtemp()
|
main_py_only_dir = tempfile.mkdtemp()
|
||||||
_temp_dirs_to_clean.append(main_py_only_dir)
|
_temp_dirs_to_clean.append(main_py_only_dir)
|
||||||
|
|
||||||
|
# skip this:
|
||||||
|
# if exists(join(args.private, "main.pyo")):
|
||||||
|
# shutil.copyfile(join(args.private, "main.pyo"),
|
||||||
|
# join(main_py_only_dir, "main.pyo"))
|
||||||
|
# elif exists(join(args.private, "main.py")):
|
||||||
|
# shutil.copyfile(join(args.private, "main.py"),
|
||||||
|
# join(main_py_only_dir, "main.py"))
|
||||||
|
# tar_dirs.append(main_py_only_dir)
|
||||||
|
|
||||||
# Check all main.py files we need to copy:
|
# Check all main.py files we need to copy:
|
||||||
copy_paths = ["main.py", join("service", "main.py")]
|
copy_paths = ["main.py", join("service", "main.py")]
|
||||||
for copy_path in copy_paths:
|
for copy_path in copy_paths:
|
||||||
|
@ -292,10 +301,10 @@ main.py that loads it.''')
|
||||||
)
|
)
|
||||||
|
|
||||||
# Append directory with all main.py's to result apk paths:
|
# Append directory with all main.py's to result apk paths:
|
||||||
private_tar_dirs.append(main_py_only_dir)
|
tar_dirs.append(main_py_only_dir)
|
||||||
if get_bootstrap_name() == "webview":
|
# if get_bootstrap_name() == "webview":
|
||||||
for asset in listdir('webview_includes'):
|
# for asset in listdir('webview_includes'):
|
||||||
shutil.copy(join('webview_includes', asset), join(assets_dir, asset))
|
# shutil.copy(join('webview_includes', asset), join(assets_dir, asset))
|
||||||
|
|
||||||
for asset in args.assets:
|
for asset in args.assets:
|
||||||
asset_src, asset_dest = asset.split(":")
|
asset_src, asset_dest = asset.split(":")
|
||||||
|
@ -316,7 +325,7 @@ main.py that loads it.''')
|
||||||
)
|
)
|
||||||
make_tar(
|
make_tar(
|
||||||
join(assets_dir, "private.tar"),
|
join(assets_dir, "private.tar"),
|
||||||
private_tar_dirs,
|
tar_dirs,
|
||||||
byte_compile_python=args.byte_compile_python,
|
byte_compile_python=args.byte_compile_python,
|
||||||
optimize_python=args.optimize_python,
|
optimize_python=args.optimize_python,
|
||||||
)
|
)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import tarfile
|
||||||
import time
|
import time
|
||||||
import subprocess
|
import subprocess
|
||||||
import shutil
|
import shutil
|
||||||
|
import json
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
import sys
|
import sys
|
||||||
from distutils.version import LooseVersion
|
from distutils.version import LooseVersion
|
||||||
|
@ -18,12 +19,58 @@ from fnmatch import fnmatch
|
||||||
|
|
||||||
import jinja2
|
import jinja2
|
||||||
|
|
||||||
|
## WHAT IS DISTINFO>JSON?
|
||||||
|
def get_dist_info_for(key):
|
||||||
|
try:
|
||||||
|
with open(join(dirname(__file__), 'dist_info.json'), 'r') as fileh:
|
||||||
|
info = json.load(fileh)
|
||||||
|
bootstrap = str(info["bootstrap"])
|
||||||
|
value = str(info[key])
|
||||||
|
except (OSError, KeyError) as e:
|
||||||
|
print("BUILD FAILURE: Couldn't extract bootstrap name " +
|
||||||
|
print("BUILD FAILURE: Couldn't extract the key `" + key + "` " +
|
||||||
|
"from dist_info.json: " + str(e))
|
||||||
|
sys.exit(1)
|
||||||
|
return bootstrap
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def get_hostpython():
|
||||||
|
return get_dist_info_for('hostpython')
|
||||||
|
|
||||||
|
def get_python_version():
|
||||||
|
return get_dist_info_for('python_version')
|
||||||
|
|
||||||
|
|
||||||
|
def get_bootstrap_name():
|
||||||
|
return get_dist_info_for('python_version')
|
||||||
|
|
||||||
|
def get_bootstrap_name():
|
||||||
|
# try:
|
||||||
|
# with open(join(dirname(__file__), 'dist_info.json'), 'r') as fileh:
|
||||||
|
# info = json.load(fileh)
|
||||||
|
# bootstrap = str(info["bootstrap"])
|
||||||
|
# except (OSError, KeyError) as e:
|
||||||
|
# print("BUILD FAILURE: Couldn't extract bootstrap name " +
|
||||||
|
# "from dist_info.json: " + str(e))
|
||||||
|
# sys.exit(1)
|
||||||
|
# return bootstrap
|
||||||
|
return get_dist_info_for('bootstrap')
|
||||||
|
|
||||||
|
|
||||||
|
if os.name == 'nt':
|
||||||
|
ANDROID = 'android.bat'
|
||||||
|
ANT = 'ant.bat'
|
||||||
|
else:
|
||||||
|
ANDROID = 'android'
|
||||||
|
ANT = 'ant'
|
||||||
|
|
||||||
curdir = dirname(__file__)
|
curdir = dirname(__file__)
|
||||||
|
|
||||||
# Try to find a host version of Python that matches our ARM version.
|
# Try to find a host version of Python that matches our ARM version.
|
||||||
PYTHON = join(curdir, 'python-install', 'bin', 'python.host')
|
# PYTHON = join(curdir, 'python-install', 'bin', 'python.host')
|
||||||
|
PYTHON = get_python_version()
|
||||||
if not exists(PYTHON):
|
if not exists(PYTHON):
|
||||||
print('Could not find hostpython, will not compile to .pyo (this is normal with python3)')
|
|
||||||
PYTHON = None
|
PYTHON = None
|
||||||
|
|
||||||
BLACKLIST_PATTERNS = [
|
BLACKLIST_PATTERNS = [
|
||||||
|
@ -125,7 +172,6 @@ def make_python_zip():
|
||||||
|
|
||||||
if not exists('private'):
|
if not exists('private'):
|
||||||
print('No compiled python is present to zip, skipping.')
|
print('No compiled python is present to zip, skipping.')
|
||||||
print('this should only be the case if you are using the CrystaX python')
|
|
||||||
return
|
return
|
||||||
|
|
||||||
global python_files
|
global python_files
|
||||||
|
@ -158,7 +204,7 @@ def make_python_zip():
|
||||||
zf.close()
|
zf.close()
|
||||||
|
|
||||||
|
|
||||||
def make_tar(tfn, source_dirs, ignore_path=[]):
|
def make_tar(tfn, source_dirs, ignore_path=[], optimize_python=True):
|
||||||
'''
|
'''
|
||||||
Make a zip file `fn` from the contents of source_dis.
|
Make a zip file `fn` from the contents of source_dis.
|
||||||
'''
|
'''
|
||||||
|
@ -179,7 +225,7 @@ def make_tar(tfn, source_dirs, ignore_path=[]):
|
||||||
files = []
|
files = []
|
||||||
for sd in source_dirs:
|
for sd in source_dirs:
|
||||||
sd = realpath(sd)
|
sd = realpath(sd)
|
||||||
compile_dir(sd)
|
compile_dir(sd, optimize_python=optimize_python)
|
||||||
files += [(x, relpath(realpath(x), sd)) for x in listfiles(sd)
|
files += [(x, relpath(realpath(x), sd)) for x in listfiles(sd)
|
||||||
if select(x)]
|
if select(x)]
|
||||||
|
|
||||||
|
@ -208,18 +254,27 @@ def make_tar(tfn, source_dirs, ignore_path=[]):
|
||||||
tf.close()
|
tf.close()
|
||||||
|
|
||||||
|
|
||||||
def compile_dir(dfn):
|
def compile_dir(dfn, optimize_python=True):
|
||||||
'''
|
'''
|
||||||
Compile *.py in directory `dfn` to *.pyo
|
Compile *.py in directory `dfn` to *.pyo
|
||||||
'''
|
'''
|
||||||
# -OO = strip docstrings
|
# -OO = strip docstrings
|
||||||
if PYTHON is None:
|
if PYTHON is None:
|
||||||
return
|
return
|
||||||
subprocess.call([PYTHON, '-OO', '-m', 'compileall', '-f', dfn])
|
args = [PYTHON, '-m', 'compileall', '-b', '-f', dfn]
|
||||||
|
if optimize_python:
|
||||||
|
# -OO = strip docstrings
|
||||||
|
args.insert(1, '-OO')
|
||||||
|
return_code = subprocess.call(args)
|
||||||
|
if return_code != 0:
|
||||||
|
print('Error while running "{}"'.format(' '.join(args)))
|
||||||
|
print('This probably means one of your Python files has a syntax '
|
||||||
|
'error, see logs above')
|
||||||
|
exit(1)
|
||||||
|
|
||||||
def make_package(args):
|
def make_package(args):
|
||||||
# Ignore warning if the launcher is in args
|
# Ignore warning if the launcher is in args
|
||||||
|
# since boostrap is 'lbry', removed get_bootstrap_name checks
|
||||||
if not args.launcher:
|
if not args.launcher:
|
||||||
if not (exists(join(realpath(args.private), 'main.py')) or
|
if not (exists(join(realpath(args.private), 'main.py')) or
|
||||||
exists(join(realpath(args.private), 'main.pyo'))):
|
exists(join(realpath(args.private), 'main.pyo'))):
|
||||||
|
@ -229,20 +284,23 @@ started by a file with a different name, rename it to main.py or add a
|
||||||
main.py that loads it.''')
|
main.py that loads it.''')
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
assets_dir = "src/main/assets"
|
||||||
|
|
||||||
# Delete the old assets.
|
# Delete the old assets.
|
||||||
try_unlink('src/main/assets/public.mp3')
|
try_unlink(join(assets_dir, 'public.mp3'))
|
||||||
try_unlink('src/main/assets/private.mp3')
|
try_unlink(join(assets_dir, 'private.mp3'))
|
||||||
|
ensure_dir(assets_dir)
|
||||||
|
|
||||||
# In order to speedup import and initial depack,
|
# In order to speedup import and initial depack,
|
||||||
# construct a python27.zip
|
# construct a python27.zip
|
||||||
|
# still here as of 12/18/18
|
||||||
make_python_zip()
|
make_python_zip()
|
||||||
|
|
||||||
# Package up the private data (public not supported).
|
# Package up the private data (public not supported).
|
||||||
tar_dirs = [args.private]
|
tar_dirs = [args.private]
|
||||||
if exists('private'):
|
for python_bundle_dir in ('private', 'crystax_python', '_python_bundle'):
|
||||||
tar_dirs.append('private')
|
if exists(python_bundle_dir):
|
||||||
if exists('crystax_python'):
|
tar_dirs.append(python_bundle_dir)
|
||||||
tar_dirs.append('crystax_python')
|
|
||||||
|
|
||||||
if args.private:
|
if args.private:
|
||||||
make_tar('src/main/assets/private.mp3', tar_dirs, args.ignore_path)
|
make_tar('src/main/assets/private.mp3', tar_dirs, args.ignore_path)
|
||||||
|
@ -255,9 +313,13 @@ main.py that loads it.''')
|
||||||
url_scheme = 'kivy'
|
url_scheme = 'kivy'
|
||||||
|
|
||||||
# Prepare some variables for templating process
|
# Prepare some variables for templating process
|
||||||
|
res_dir = "src/main/res"
|
||||||
|
# bootstrap is lbry so we def need these
|
||||||
default_icon = 'templates/lbry-icon.png'
|
default_icon = 'templates/lbry-icon.png'
|
||||||
default_presplash = 'templates/kivy-presplash.jpg'
|
default_presplash = 'templates/kivy-presplash.jpg'
|
||||||
shutil.copy(args.icon or default_icon, 'src/main/res/drawable/icon.png')
|
shutil.copy(args.icon or default_icon,
|
||||||
|
join(res_dir, 'drawable/icon.png')
|
||||||
|
)
|
||||||
shutil.copy(args.presplash or default_presplash,
|
shutil.copy(args.presplash or default_presplash,
|
||||||
'src/main/res/drawable/presplash.jpg')
|
'src/main/res/drawable/presplash.jpg')
|
||||||
|
|
||||||
|
@ -324,9 +386,15 @@ main.py that loads it.''')
|
||||||
sticky = 'sticky' in options
|
sticky = 'sticky' in options
|
||||||
|
|
||||||
service_names.append(name)
|
service_names.append(name)
|
||||||
|
# 12/18/18
|
||||||
|
service_target_path = \
|
||||||
|
'src/main/java/{}/Service{}.java'.format(
|
||||||
|
args.package.replace(".", "/"),
|
||||||
|
name.capitalize()
|
||||||
|
)
|
||||||
render(
|
render(
|
||||||
'Service.tmpl.java',
|
'Service.tmpl.java',
|
||||||
'src/main/java/{}/Service{}.java'.format(args.package.replace(".", "/"), name.capitalize()),
|
service_target_path,
|
||||||
name=name,
|
name=name,
|
||||||
entrypoint=entrypoint,
|
entrypoint=entrypoint,
|
||||||
args=args,
|
args=args,
|
||||||
|
@ -359,11 +427,13 @@ main.py that loads it.''')
|
||||||
|
|
||||||
# Copy the AndroidManifest.xml to the dist root dir so that ant
|
# Copy the AndroidManifest.xml to the dist root dir so that ant
|
||||||
# can also use it
|
# can also use it
|
||||||
|
manifest_path = "src/main/AndroidManifest.xml"
|
||||||
if exists('AndroidManifest.xml'):
|
if exists('AndroidManifest.xml'):
|
||||||
remove('AndroidManifest.xml')
|
remove('AndroidManifest.xml')
|
||||||
shutil.copy(join('src', 'main', 'AndroidManifest.xml'),
|
shutil.copy(join('src', 'main', 'AndroidManifest.xml'),
|
||||||
'AndroidManifest.xml')
|
'AndroidManifest.xml')
|
||||||
|
|
||||||
|
# String resources
|
||||||
render(
|
render(
|
||||||
'strings.tmpl.xml',
|
'strings.tmpl.xml',
|
||||||
'src/main/res/values/strings.xml',
|
'src/main/res/values/strings.xml',
|
||||||
|
@ -423,6 +493,7 @@ main.py that loads it.''')
|
||||||
args=args,
|
args=args,
|
||||||
versioned_name=versioned_name)
|
versioned_name=versioned_name)
|
||||||
|
|
||||||
|
if exists(join("templates", "custom_rules.tmpl.xml")):
|
||||||
render(
|
render(
|
||||||
'custom_rules.tmpl.xml',
|
'custom_rules.tmpl.xml',
|
||||||
'custom_rules.xml',
|
'custom_rules.xml',
|
||||||
|
@ -435,9 +506,44 @@ main.py that loads it.''')
|
||||||
if exists('build.properties'):
|
if exists('build.properties'):
|
||||||
os.remove('build.properties')
|
os.remove('build.properties')
|
||||||
|
|
||||||
|
# # Apply java source patches if any are present:
|
||||||
|
# if exists(join('src', 'patches')):
|
||||||
|
# print("Applying Java source code patches...")
|
||||||
|
# for patch_name in os.listdir(join('src', 'patches')):
|
||||||
|
# patch_path = join('src', 'patches', patch_name)
|
||||||
|
# print("Applying patch: " + str(patch_path))
|
||||||
|
# try:
|
||||||
|
# subprocess.check_output([
|
||||||
|
# # -N: insist this is FORWARd patch, don't reverse apply
|
||||||
|
# # -p1: strip first path component
|
||||||
|
# # -t: batch mode, don't ask questions
|
||||||
|
# "patch", "-N", "-p1", "-t", "-i", patch_path
|
||||||
|
# ])
|
||||||
|
# except subprocess.CalledProcessError as e:
|
||||||
|
# if e.returncode == 1:
|
||||||
|
# # Return code 1 means it didn't apply, this will
|
||||||
|
# # usually mean it is already applied.
|
||||||
|
# print("Warning: failed to apply patch (" +
|
||||||
|
# "exit code 1), " +
|
||||||
|
# "assuming it is already applied: " +
|
||||||
|
# str(patch_path)
|
||||||
|
# )
|
||||||
|
# else:
|
||||||
|
# raise e
|
||||||
|
|
||||||
def parse_args(args=None):
|
def parse_args(args=None):
|
||||||
global BLACKLIST_PATTERNS, WHITELIST_PATTERNS, PYTHON
|
global BLACKLIST_PATTERNS, WHITELIST_PATTERNS, PYTHON
|
||||||
default_android_api = 12
|
default_android_api = 21
|
||||||
|
|
||||||
|
# Get the default minsdk, equal to the NDK API that this dist it built against
|
||||||
|
with open('dist_info.json', 'r') as fileh:
|
||||||
|
info = json.load(fileh)
|
||||||
|
if 'ndk_api' not in info:
|
||||||
|
print('Failed to read ndk_api from dist info')
|
||||||
|
default_android_api = 12 # The old default before ndk_api was introduced
|
||||||
|
else:
|
||||||
|
default_android_api = info['ndk_api']
|
||||||
|
ndk_api = info['ndk_api']
|
||||||
import argparse
|
import argparse
|
||||||
ap = argparse.ArgumentParser(description='''\
|
ap = argparse.ArgumentParser(description='''\
|
||||||
Package a Python application for Android.
|
Package a Python application for Android.
|
||||||
|
@ -551,8 +657,18 @@ tools directory of the Android SDK.
|
||||||
if args.name and args.name[0] == '"' and args.name[-1] == '"':
|
if args.name and args.name[0] == '"' and args.name[-1] == '"':
|
||||||
args.name = args.name[1:-1]
|
args.name = args.name[1:-1]
|
||||||
|
|
||||||
# if args.sdk_version == -1:
|
if ndk_api != args.min_sdk_version:
|
||||||
# args.sdk_version = args.min_sdk_version
|
print(('WARNING: --minsdk argument does not match the api that is '
|
||||||
|
'compiled against. Only proceed if you know what you are '
|
||||||
|
'doing, otherwise use --minsdk={} or recompile against api '
|
||||||
|
'{}').format(ndk_api, args.min_sdk_version))
|
||||||
|
if not args.allow_minsdk_ndkapi_mismatch:
|
||||||
|
print('You must pass --allow-minsdk-ndkapi-mismatch to build '
|
||||||
|
'with --minsdk different to the target NDK api from the '
|
||||||
|
'build step')
|
||||||
|
exit(1)
|
||||||
|
else:
|
||||||
|
print('Proceeding with --minsdk not matching build target api')
|
||||||
|
|
||||||
if args.sdk_version != -1:
|
if args.sdk_version != -1:
|
||||||
print('WARNING: Received a --sdk argument, but this argument is '
|
print('WARNING: Received a --sdk argument, but this argument is '
|
||||||
|
@ -573,7 +689,7 @@ tools directory of the Android SDK.
|
||||||
if args.try_system_python_compile:
|
if args.try_system_python_compile:
|
||||||
# Hardcoding python2.7 is okay for now, as python3 skips the
|
# Hardcoding python2.7 is okay for now, as python3 skips the
|
||||||
# compilation anyway
|
# compilation anyway
|
||||||
if not exists('crystax_python'):
|
|
||||||
python_executable = 'python2.7'
|
python_executable = 'python2.7'
|
||||||
try:
|
try:
|
||||||
subprocess.call([python_executable, '--version'])
|
subprocess.call([python_executable, '--version'])
|
||||||
|
@ -598,6 +714,9 @@ tools directory of the Android SDK.
|
||||||
if x.strip() and not x.strip().startswith('#')]
|
if x.strip() and not x.strip().startswith('#')]
|
||||||
WHITELIST_PATTERNS += patterns
|
WHITELIST_PATTERNS += patterns
|
||||||
|
|
||||||
|
if args.private is None:
|
||||||
|
print('Need --private directory with app files to package for .apk')
|
||||||
|
exit(1)
|
||||||
make_package(args)
|
make_package(args)
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
10
p4a/setup.py
10
p4a/setup.py
|
@ -23,7 +23,7 @@ else:
|
||||||
# don't use sh after 1.12.5, we have performance issues
|
# don't use sh after 1.12.5, we have performance issues
|
||||||
# https://github.com/amoffat/sh/issues/378
|
# https://github.com/amoffat/sh/issues/378
|
||||||
install_reqs = ['appdirs', 'colorama>=0.3.3', 'sh>=1.10,<1.12.5', 'jinja2',
|
install_reqs = ['appdirs', 'colorama>=0.3.3', 'sh>=1.10,<1.12.5', 'jinja2',
|
||||||
'six']
|
'six', 'enum34']
|
||||||
|
|
||||||
# By specifying every file manually, package_data will be able to
|
# By specifying every file manually, package_data will be able to
|
||||||
# include them in binary distributions. Note that we have to add
|
# include them in binary distributions. Note that we have to add
|
||||||
|
@ -54,13 +54,17 @@ recursively_include(package_data, 'pythonforandroid/bootstraps/webview',
|
||||||
recursively_include(package_data, 'pythonforandroid',
|
recursively_include(package_data, 'pythonforandroid',
|
||||||
['liblink', 'biglink', 'liblink.sh'])
|
['liblink', 'biglink', 'liblink.sh'])
|
||||||
|
|
||||||
with open(join(dirname(__file__), 'README.rst')) as fileh:
|
with open(join(dirname(__file__), 'README.md',
|
||||||
|
encoding="utf-8",
|
||||||
|
errors="replace")) as fileh:
|
||||||
long_description = fileh.read()
|
long_description = fileh.read()
|
||||||
|
|
||||||
init_filen = join(dirname(__file__), 'pythonforandroid', '__init__.py')
|
init_filen = join(dirname(__file__), 'pythonforandroid', '__init__.py')
|
||||||
version = None
|
version = None
|
||||||
try:
|
try:
|
||||||
with open(init_filen) as fileh:
|
with open(init_filen,
|
||||||
|
encoding="utf-8",
|
||||||
|
errors="replace") as fileh:
|
||||||
lines = fileh.readlines()
|
lines = fileh.readlines()
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
pass
|
||||||
|
|
Loading…
Reference in a new issue