Skip to content

Commit 4ebfc08

Browse files
committed
[build-script] Modularize argparse types
1 parent 9280536 commit 4ebfc08

File tree

3 files changed

+196
-57
lines changed

3 files changed

+196
-57
lines changed

utils/build-script

Lines changed: 12 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ import multiprocessing
1616
import os
1717
import pipes
1818
import platform
19-
import re
20-
import shlex
2119
import sys
2220

2321
# FIXME: Instead of modifying the system path in order to enable imports from
@@ -40,6 +38,7 @@ from SwiftBuildSupport import (
4038
sys.path.append(os.path.join(os.path.dirname(__file__), 'swift_build_support'))
4139

4240
# E402 means module level import not at top of file
41+
from swift_build_support import arguments # noqa (E402)
4342
from swift_build_support.toolchain import host_toolchain # noqa (E402)
4443
import swift_build_support.debug # noqa (E402)
4544
from swift_build_support import migration # noqa (E402)
@@ -52,50 +51,6 @@ from swift_build_support.workspace import Workspace # noqa(E402)
5251
from swift_build_support.workspace import compute_build_subdir # noqa(E402)
5352

5453

55-
# A strict parser for bools (unlike Python's `bool()`, where
56-
# `bool('False')` is `True`).
57-
#
58-
# This function can be passed as `type=` argument to argparse to parse values
59-
# passed to command line arguments.
60-
def argparse_bool(string):
61-
if string in ['0', 'false', 'False']:
62-
return False
63-
if string in ['1', 'true', 'True']:
64-
return True
65-
raise argparse.ArgumentTypeError("%r is not a boolean value" % string)
66-
67-
68-
# Parse and split shell arguments string into a list of shell arguments.
69-
# Recognize `,` as a separator as well as white spaces.
70-
# string: -BAR="foo bar" -BAZ='foo,bar',-QUX 42
71-
# into
72-
# ['-BAR=foo bar', '-BAZ=foo,bar', "-QUX", "42"]
73-
def argparse_shell_split(string):
74-
lex = shlex.shlex(string, posix=True)
75-
lex.whitespace_split = True
76-
lex.whitespace += ','
77-
return list(lex)
78-
79-
80-
# Parse version string and split into a tuple of strings (major, minor, patch)
81-
# Support only "MAJOR.MINOR.PATCH" format.
82-
def argparse_clang_compiler_version(string):
83-
m = re.match(r'([0-9]*)\.([0-9]*)\.([0-9]*)', string)
84-
if m is not None:
85-
return m.group(1, 2, 3)
86-
raise argparse.ArgumentTypeError(
87-
"%r is invalid version value. must be 'MAJOR.MINOR.PATCH'" % string)
88-
89-
90-
# Check the string is executable path string.
91-
# Convert it to absolute path.
92-
def argparse_executable(string):
93-
if os.access(string, os.X_OK):
94-
return os.path.abspath(string)
95-
raise argparse.ArgumentTypeError(
96-
"%r is not executable" % string)
97-
98-
9954
# Main entry point for the preset mode.
10055
def main_preset():
10156
parser = argparse.ArgumentParser(
@@ -591,7 +546,7 @@ details of the setups of other systems or automated environments.""")
591546
help="test Swift after building",
592547
metavar="BOOL",
593548
nargs='?',
594-
type=argparse_bool,
549+
type=arguments.type.bool,
595550
default=False,
596551
const=True)
597552
run_tests_group.add_argument(
@@ -605,7 +560,7 @@ details of the setups of other systems or automated environments.""")
605560
help="run the validation test suite (implies --test)",
606561
metavar="BOOL",
607562
nargs='?',
608-
type=argparse_bool,
563+
type=arguments.type.bool,
609564
default=False,
610565
const=True)
611566
run_tests_group.add_argument(
@@ -619,15 +574,15 @@ details of the setups of other systems or automated environments.""")
619574
help="run the test suite in optimized mode too (implies --test)",
620575
metavar="BOOL",
621576
nargs='?',
622-
type=argparse_bool,
577+
type=arguments.type.bool,
623578
default=False,
624579
const=True)
625580
run_tests_group.add_argument(
626581
"--long-test",
627582
help="run the long test suite",
628583
metavar="BOOL",
629584
nargs='?',
630-
type=argparse_bool,
585+
type=arguments.type.bool,
631586
default=False,
632587
const=True)
633588
run_tests_group.add_argument(
@@ -860,7 +815,7 @@ details of the setups of other systems or automated environments.""")
860815
"--cmake",
861816
help="the path to a CMake executable that will be used to build "
862817
"Swift",
863-
type=argparse_executable,
818+
type=arguments.type.executable,
864819
metavar="PATH")
865820
parser.add_argument(
866821
"--show-sdks",
@@ -916,13 +871,13 @@ details of the setups of other systems or automated environments.""")
916871
"--host-cc",
917872
help="the absolute path to CC, the 'clang' compiler for the host "
918873
"platform. Default is auto detected.",
919-
type=argparse_executable,
874+
type=arguments.type.executable,
920875
metavar="PATH")
921876
parser.add_argument(
922877
"--host-cxx",
923878
help="the absolute path to CXX, the 'clang++' compiler for the host "
924879
"platform. Default is auto detected.",
925-
type=argparse_executable,
880+
type=arguments.type.executable,
926881
metavar="PATH")
927882
parser.add_argument(
928883
"--distcc",
@@ -939,7 +894,7 @@ details of the setups of other systems or automated environments.""")
939894
parser.add_argument(
940895
"--clang-compiler-version",
941896
help="string that indicates a compiler version for Clang",
942-
type=argparse_clang_compiler_version,
897+
type=arguments.type.clang_compiler_version,
943898
metavar="MAJOR.MINOR.PATCH")
944899

945900
parser.add_argument(
@@ -969,23 +924,23 @@ details of the setups of other systems or automated environments.""")
969924
"separated options '-DCMAKE_VAR1=YES,-DCMAKE_VAR2=/tmp'. Can be "
970925
"called multiple times to add multiple such options.",
971926
action="append",
972-
type=argparse_shell_split,
927+
type=arguments.type.shell_split,
973928
default=[])
974929

975930
parser.add_argument(
976931
"--build-args",
977932
help="arguments to the build tool. This would be prepended to the "
978933
"default argument that is '-j8' when CMake generator is "
979934
"\"Ninja\".",
980-
type=argparse_shell_split,
935+
type=arguments.type.shell_split,
981936
default=[])
982937

983938
parser.add_argument(
984939
"--verbose-build",
985940
help="print the commands executed during the build",
986941
metavar="BOOL",
987942
nargs='?',
988-
type=argparse_bool,
943+
type=arguments.type.bool,
989944
default=False,
990945
const=True)
991946

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# swift_build_support/arguments.py ------------------------------*- python -*-
2+
#
3+
# This source file is part of the Swift.org open source project
4+
#
5+
# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
6+
# Licensed under Apache License v2.0 with Runtime Library Exception
7+
#
8+
# See http://swift.org/LICENSE.txt for license information
9+
# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
#
11+
# ----------------------------------------------------------------------------
12+
"""
13+
argparse suppliments
14+
"""
15+
# ----------------------------------------------------------------------------
16+
17+
from __future__ import absolute_import
18+
19+
import argparse
20+
import os
21+
import re
22+
import shlex
23+
24+
__all__ = [
25+
"type"
26+
]
27+
28+
29+
class _Registry(object):
30+
pass
31+
32+
33+
def _register(registry, name, value):
34+
setattr(registry, name, value)
35+
36+
37+
type = _Registry()
38+
39+
40+
def type_bool(string):
41+
"""
42+
A strict parser for bools
43+
44+
unlike Python's `bool()`, where `bool('False')` is `True`
45+
This function can be passed as `type=` argument to argparse to parse values
46+
passed to command line arguments.
47+
"""
48+
if string in ['0', 'false', 'False']:
49+
return False
50+
if string in ['1', 'true', 'True']:
51+
return True
52+
raise argparse.ArgumentTypeError("%r is not a boolean value" % string)
53+
54+
_register(type, 'bool', type_bool)
55+
56+
57+
def type_shell_split(string):
58+
"""
59+
Parse and split shell arguments string into a list of shell arguments.
60+
61+
Recognize `,` as a separator as well as white spaces.
62+
string: -BAR="foo bar" -BAZ='foo,bar',-QUX 42
63+
into
64+
['-BAR=foo bar', '-BAZ=foo,bar', "-QUX", "42"]
65+
"""
66+
lex = shlex.shlex(string, posix=True)
67+
lex.whitespace_split = True
68+
lex.whitespace += ','
69+
return list(lex)
70+
71+
_register(type, 'shell_split', type_shell_split)
72+
73+
74+
def type_clang_compiler_version(string):
75+
"""
76+
Parse version string and split into a tuple of strings
77+
(major, minor, patch)
78+
79+
Support only "MAJOR.MINOR.PATCH" format.
80+
"""
81+
m = re.match(r'([0-9]*)\.([0-9]*)\.([0-9]*)', string)
82+
if m is not None:
83+
return m.group(1, 2, 3)
84+
raise argparse.ArgumentTypeError(
85+
"%r is invalid version value. must be 'MAJOR.MINOR.PATCH'" % string)
86+
87+
_register(type, 'clang_compiler_version', type_clang_compiler_version)
88+
89+
90+
def type_executable(string):
91+
"""
92+
Check the string is executable path string.
93+
94+
Convert it to absolute path.
95+
"""
96+
if os.access(string, os.X_OK):
97+
return os.path.abspath(string)
98+
raise argparse.ArgumentTypeError(
99+
"%r is not executable" % string)
100+
101+
_register(type, 'executable', type_executable)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# tests/arguments.py --------------------------------------------*- python -*-
2+
#
3+
# This source file is part of the Swift.org open source project
4+
#
5+
# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
6+
# Licensed under Apache License v2.0 with Runtime Library Exception
7+
#
8+
# See http://swift.org/LICENSE.txt for license information
9+
# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
#
11+
# ----------------------------------------------------------------------------
12+
13+
import argparse
14+
import os
15+
import sys
16+
import unittest
17+
18+
from swift_build_support.arguments import type as argtype
19+
20+
21+
class ArgumentsTypeTestCase(unittest.TestCase):
22+
23+
def test_bool(self):
24+
self.assertTrue(argtype.bool("1"))
25+
self.assertTrue(argtype.bool("true"))
26+
self.assertTrue(argtype.bool("True"))
27+
28+
self.assertFalse(argtype.bool("0"))
29+
self.assertFalse(argtype.bool("false"))
30+
self.assertFalse(argtype.bool("False"))
31+
32+
self.assertRaises(argparse.ArgumentTypeError, argtype.bool, 'foobar')
33+
self.assertRaises(argparse.ArgumentTypeError, argtype.bool, 'TRUE')
34+
self.assertRaises(argparse.ArgumentTypeError, argtype.bool, 'FALSE')
35+
36+
def test_shell_split(self):
37+
self.assertEqual(
38+
argtype.shell_split("-BAR=\"foo bar\" -BAZ='foo,bar',-QUX $baz"),
39+
['-BAR=foo bar', '-BAZ=foo,bar', '-QUX', '$baz'])
40+
41+
def test_clang_compiler_version(self):
42+
self.assertEqual(
43+
argtype.clang_compiler_version('1.23.456'),
44+
("1", "23", "456"))
45+
self.assertRaises(
46+
argparse.ArgumentTypeError,
47+
argtype.clang_compiler_version,
48+
"ver1.2.3")
49+
self.assertRaises(
50+
argparse.ArgumentTypeError,
51+
argtype.clang_compiler_version,
52+
"1.beta2.3")
53+
self.assertEqual(
54+
argtype.clang_compiler_version("1.2.preview3"),
55+
("1", "2", ""))
56+
self.assertEqual(
57+
argtype.clang_compiler_version("1.2.3-rc4"),
58+
("1", "2", "3"))
59+
60+
def test_executable(self):
61+
python = sys.executable
62+
self.assertTrue(os.path.isabs(argtype.executable(python)))
63+
64+
# On this test directory, specifying "../../build-script-impl" returns
65+
# absolute path of build-script-impl
66+
impl = os.path.join("..", "..", "build-script-impl")
67+
cwd = os.getcwd()
68+
os.chdir(os.path.dirname(__file__))
69+
self.assertTrue(os.path.isabs(argtype.executable(impl)))
70+
os.chdir(cwd)
71+
72+
self.assertRaises(
73+
argparse.ArgumentTypeError,
74+
argtype.executable, __file__) # this file is not executable
75+
self.assertRaises(
76+
argparse.ArgumentTypeError,
77+
argtype.executable, os.path.dirname(__file__))
78+
self.assertRaises(
79+
argparse.ArgumentTypeError,
80+
argtype.executable, "/bin/example-command-not-exist")
81+
self.assertRaises(
82+
argparse.ArgumentTypeError,
83+
argtype.executable, "../example-command-not-exist")

0 commit comments

Comments
 (0)