Show More
@@ -0,0 +1,145 b'' | |||||
|
1 | # pyoxidizer.py - Packaging support for PyOxidizer | |||
|
2 | # | |||
|
3 | # Copyright 2020 Gregory Szorc <gregory.szorc@gmail.com> | |||
|
4 | # | |||
|
5 | # This software may be used and distributed according to the terms of the | |||
|
6 | # GNU General Public License version 2 or any later version. | |||
|
7 | ||||
|
8 | # no-check-code because Python 3 native. | |||
|
9 | ||||
|
10 | import os | |||
|
11 | import pathlib | |||
|
12 | import shutil | |||
|
13 | import subprocess | |||
|
14 | import sys | |||
|
15 | ||||
|
16 | from .downloads import download_entry | |||
|
17 | from .util import ( | |||
|
18 | extract_zip_to_directory, | |||
|
19 | process_install_rules, | |||
|
20 | find_vc_runtime_dll, | |||
|
21 | ) | |||
|
22 | ||||
|
23 | ||||
|
24 | STAGING_RULES_WINDOWS = [ | |||
|
25 | ('contrib/bash_completion', 'contrib/'), | |||
|
26 | ('contrib/hgk', 'contrib/hgk.tcl'), | |||
|
27 | ('contrib/hgweb.fcgi', 'contrib/'), | |||
|
28 | ('contrib/hgweb.wsgi', 'contrib/'), | |||
|
29 | ('contrib/logo-droplets.svg', 'contrib/'), | |||
|
30 | ('contrib/mercurial.el', 'contrib/'), | |||
|
31 | ('contrib/mq.el', 'contrib/'), | |||
|
32 | ('contrib/tcsh_completion', 'contrib/'), | |||
|
33 | ('contrib/tcsh_completion_build.sh', 'contrib/'), | |||
|
34 | ('contrib/vim/*', 'contrib/vim/'), | |||
|
35 | ('contrib/win32/postinstall.txt', 'ReleaseNotes.txt'), | |||
|
36 | ('contrib/win32/ReadMe.html', 'ReadMe.html'), | |||
|
37 | ('contrib/xml.rnc', 'contrib/'), | |||
|
38 | ('contrib/zsh_completion', 'contrib/'), | |||
|
39 | ('doc/*.html', 'doc/'), | |||
|
40 | ('doc/style.css', 'doc/'), | |||
|
41 | ('COPYING', 'Copying.txt'), | |||
|
42 | ] | |||
|
43 | ||||
|
44 | STAGING_RULES_APP = [ | |||
|
45 | ('mercurial/helptext/**/*.txt', 'helptext/'), | |||
|
46 | ('mercurial/defaultrc/*.rc', 'defaultrc/'), | |||
|
47 | ('mercurial/locale/**/*', 'locale/'), | |||
|
48 | ('mercurial/templates/**/*', 'templates/'), | |||
|
49 | ] | |||
|
50 | ||||
|
51 | STAGING_EXCLUDES_WINDOWS = [ | |||
|
52 | "doc/hg-ssh.8.html", | |||
|
53 | ] | |||
|
54 | ||||
|
55 | ||||
|
56 | def run_pyoxidizer( | |||
|
57 | source_dir: pathlib.Path, | |||
|
58 | build_dir: pathlib.Path, | |||
|
59 | out_dir: pathlib.Path, | |||
|
60 | target_triple: str, | |||
|
61 | ): | |||
|
62 | """Build Mercurial with PyOxidizer and copy additional files into place. | |||
|
63 | ||||
|
64 | After successful completion, ``out_dir`` contains files constituting a | |||
|
65 | Mercurial install. | |||
|
66 | """ | |||
|
67 | # We need to make gettext binaries available for compiling i18n files. | |||
|
68 | gettext_pkg, gettext_entry = download_entry('gettext', build_dir) | |||
|
69 | gettext_dep_pkg = download_entry('gettext-dep', build_dir)[0] | |||
|
70 | ||||
|
71 | gettext_root = build_dir / ('gettext-win-%s' % gettext_entry['version']) | |||
|
72 | ||||
|
73 | if not gettext_root.exists(): | |||
|
74 | extract_zip_to_directory(gettext_pkg, gettext_root) | |||
|
75 | extract_zip_to_directory(gettext_dep_pkg, gettext_root) | |||
|
76 | ||||
|
77 | env = dict(os.environ) | |||
|
78 | env["PATH"] = "%s%s%s" % ( | |||
|
79 | env["PATH"], | |||
|
80 | os.pathsep, | |||
|
81 | str(gettext_root / "bin"), | |||
|
82 | ) | |||
|
83 | ||||
|
84 | args = [ | |||
|
85 | "pyoxidizer", | |||
|
86 | "build", | |||
|
87 | "--path", | |||
|
88 | str(source_dir / "rust" / "hgcli"), | |||
|
89 | "--release", | |||
|
90 | "--target-triple", | |||
|
91 | target_triple, | |||
|
92 | ] | |||
|
93 | ||||
|
94 | subprocess.run(args, env=env, check=True) | |||
|
95 | ||||
|
96 | if "windows" in target_triple: | |||
|
97 | target = "app_windows" | |||
|
98 | else: | |||
|
99 | target = "app_posix" | |||
|
100 | ||||
|
101 | build_dir = ( | |||
|
102 | source_dir / "build" / "pyoxidizer" / target_triple / "release" / target | |||
|
103 | ) | |||
|
104 | ||||
|
105 | if out_dir.exists(): | |||
|
106 | print("purging %s" % out_dir) | |||
|
107 | shutil.rmtree(out_dir) | |||
|
108 | ||||
|
109 | # Now assemble all the files from PyOxidizer into the staging directory. | |||
|
110 | shutil.copytree(build_dir, out_dir) | |||
|
111 | ||||
|
112 | # Move some of those files around. | |||
|
113 | process_install_rules(STAGING_RULES_APP, build_dir, out_dir) | |||
|
114 | # Nuke the mercurial/* directory, as we copied resources | |||
|
115 | # to an appropriate location just above. | |||
|
116 | shutil.rmtree(out_dir / "mercurial") | |||
|
117 | ||||
|
118 | # We also need to run setup.py build_doc to produce html files, | |||
|
119 | # as they aren't built as part of ``pip install``. | |||
|
120 | # This will fail if docutils isn't installed. | |||
|
121 | subprocess.run( | |||
|
122 | [sys.executable, str(source_dir / "setup.py"), "build_doc", "--html"], | |||
|
123 | cwd=str(source_dir), | |||
|
124 | check=True, | |||
|
125 | ) | |||
|
126 | ||||
|
127 | if "windows" in target_triple: | |||
|
128 | process_install_rules(STAGING_RULES_WINDOWS, source_dir, out_dir) | |||
|
129 | ||||
|
130 | # Write out a default editor.rc file to configure notepad as the | |||
|
131 | # default editor. | |||
|
132 | with (out_dir / "defaultrc" / "editor.rc").open( | |||
|
133 | "w", encoding="utf-8" | |||
|
134 | ) as fh: | |||
|
135 | fh.write("[ui]\neditor = notepad\n") | |||
|
136 | ||||
|
137 | for f in STAGING_EXCLUDES_WINDOWS: | |||
|
138 | p = out_dir / f | |||
|
139 | if p.exists(): | |||
|
140 | print("removing %s" % p) | |||
|
141 | p.unlink() | |||
|
142 | ||||
|
143 | # Add vcruntimeXXX.dll next to executable. | |||
|
144 | vc_runtime_dll = find_vc_runtime_dll(x64="x86_64" in target_triple) | |||
|
145 | shutil.copy(vc_runtime_dll, out_dir / vc_runtime_dll.name) |
@@ -20,8 +20,11 b' HERE = pathlib.Path(os.path.abspath(os.p' | |||||
20 | SOURCE_DIR = HERE.parent.parent.parent |
|
20 | SOURCE_DIR = HERE.parent.parent.parent | |
21 |
|
21 | |||
22 |
|
22 | |||
23 | def build_inno(python=None, iscc=None, version=None): |
|
23 | def build_inno(pyoxidizer_target=None, python=None, iscc=None, version=None): | |
24 | if not os.path.isabs(python): |
|
24 | if not pyoxidizer_target and not python: | |
|
25 | raise Exception("--python required unless building with PyOxidizer") | |||
|
26 | ||||
|
27 | if python and not os.path.isabs(python): | |||
25 | raise Exception("--python arg must be an absolute path") |
|
28 | raise Exception("--python arg must be an absolute path") | |
26 |
|
29 | |||
27 | if iscc: |
|
30 | if iscc: | |
@@ -35,7 +38,12 b' def build_inno(python=None, iscc=None, v' | |||||
35 |
|
38 | |||
36 | build_dir = SOURCE_DIR / "build" |
|
39 | build_dir = SOURCE_DIR / "build" | |
37 |
|
40 | |||
38 | inno.build( |
|
41 | if pyoxidizer_target: | |
|
42 | inno.build_with_pyoxidizer( | |||
|
43 | SOURCE_DIR, build_dir, pyoxidizer_target, iscc, version=version | |||
|
44 | ) | |||
|
45 | else: | |||
|
46 | inno.build_with_py2exe( | |||
39 | SOURCE_DIR, build_dir, pathlib.Path(python), iscc, version=version, |
|
47 | SOURCE_DIR, build_dir, pathlib.Path(python), iscc, version=version, | |
40 | ) |
|
48 | ) | |
41 |
|
49 | |||
@@ -88,7 +96,12 b' def get_parser():' | |||||
88 | subparsers = parser.add_subparsers() |
|
96 | subparsers = parser.add_subparsers() | |
89 |
|
97 | |||
90 | sp = subparsers.add_parser("inno", help="Build Inno Setup installer") |
|
98 | sp = subparsers.add_parser("inno", help="Build Inno Setup installer") | |
91 | sp.add_argument("--python", required=True, help="path to python.exe to use") |
|
99 | sp.add_argument( | |
|
100 | "--pyoxidizer-target", | |||
|
101 | choices={"i686-pc-windows-msvc", "x86_64-pc-windows-msvc"}, | |||
|
102 | help="Build with PyOxidizer targeting this host triple", | |||
|
103 | ) | |||
|
104 | sp.add_argument("--python", help="path to python.exe to use") | |||
92 | sp.add_argument("--iscc", help="path to iscc.exe to use") |
|
105 | sp.add_argument("--iscc", help="path to iscc.exe to use") | |
93 | sp.add_argument( |
|
106 | sp.add_argument( | |
94 | "--version", |
|
107 | "--version", |
@@ -18,8 +18,9 b' from .py2exe import (' | |||||
18 | build_py2exe, |
|
18 | build_py2exe, | |
19 | stage_install, |
|
19 | stage_install, | |
20 | ) |
|
20 | ) | |
|
21 | from .pyoxidizer import run_pyoxidizer | |||
21 | from .util import ( |
|
22 | from .util import ( | |
22 | find_vc_runtime_files, |
|
23 | find_legacy_vc_runtime_files, | |
23 | normalize_windows_version, |
|
24 | normalize_windows_version, | |
24 | process_install_rules, |
|
25 | process_install_rules, | |
25 | read_version_py, |
|
26 | read_version_py, | |
@@ -41,14 +42,14 b' PACKAGE_FILES_METADATA = {' | |||||
41 | } |
|
42 | } | |
42 |
|
43 | |||
43 |
|
44 | |||
44 | def build( |
|
45 | def build_with_py2exe( | |
45 | source_dir: pathlib.Path, |
|
46 | source_dir: pathlib.Path, | |
46 | build_dir: pathlib.Path, |
|
47 | build_dir: pathlib.Path, | |
47 | python_exe: pathlib.Path, |
|
48 | python_exe: pathlib.Path, | |
48 | iscc_exe: pathlib.Path, |
|
49 | iscc_exe: pathlib.Path, | |
49 | version=None, |
|
50 | version=None, | |
50 | ): |
|
51 | ): | |
51 | """Build the Inno installer. |
|
52 | """Build the Inno installer using py2exe. | |
52 |
|
53 | |||
53 | Build files will be placed in ``build_dir``. |
|
54 | Build files will be placed in ``build_dir``. | |
54 |
|
55 | |||
@@ -92,7 +93,7 b' def build(' | |||||
92 | process_install_rules(EXTRA_INSTALL_RULES, source_dir, staging_dir) |
|
93 | process_install_rules(EXTRA_INSTALL_RULES, source_dir, staging_dir) | |
93 |
|
94 | |||
94 | # hg.exe depends on VC9 runtime DLLs. Copy those into place. |
|
95 | # hg.exe depends on VC9 runtime DLLs. Copy those into place. | |
95 | for f in find_vc_runtime_files(vc_x64): |
|
96 | for f in find_legacy_vc_runtime_files(vc_x64): | |
96 | if f.name.endswith('.manifest'): |
|
97 | if f.name.endswith('.manifest'): | |
97 | basename = 'Microsoft.VC90.CRT.manifest' |
|
98 | basename = 'Microsoft.VC90.CRT.manifest' | |
98 | else: |
|
99 | else: | |
@@ -113,6 +114,35 b' def build(' | |||||
113 | ) |
|
114 | ) | |
114 |
|
115 | |||
115 |
|
116 | |||
|
117 | def build_with_pyoxidizer( | |||
|
118 | source_dir: pathlib.Path, | |||
|
119 | build_dir: pathlib.Path, | |||
|
120 | target_triple: str, | |||
|
121 | iscc_exe: pathlib.Path, | |||
|
122 | version=None, | |||
|
123 | ): | |||
|
124 | """Build the Inno installer using PyOxidizer.""" | |||
|
125 | if not iscc_exe.exists(): | |||
|
126 | raise Exception("%s does not exist" % iscc_exe) | |||
|
127 | ||||
|
128 | inno_build_dir = build_dir / ("inno-pyoxidizer-%s" % target_triple) | |||
|
129 | staging_dir = inno_build_dir / "stage" | |||
|
130 | ||||
|
131 | inno_build_dir.mkdir(parents=True, exist_ok=True) | |||
|
132 | run_pyoxidizer(source_dir, inno_build_dir, staging_dir, target_triple) | |||
|
133 | ||||
|
134 | process_install_rules(EXTRA_INSTALL_RULES, source_dir, staging_dir) | |||
|
135 | ||||
|
136 | build_installer( | |||
|
137 | source_dir, | |||
|
138 | inno_build_dir, | |||
|
139 | staging_dir, | |||
|
140 | iscc_exe, | |||
|
141 | version, | |||
|
142 | arch="x64" if "x86_64" in target_triple else None, | |||
|
143 | ) | |||
|
144 | ||||
|
145 | ||||
116 | def build_installer( |
|
146 | def build_installer( | |
117 | source_dir: pathlib.Path, |
|
147 | source_dir: pathlib.Path, | |
118 | inno_build_dir: pathlib.Path, |
|
148 | inno_build_dir: pathlib.Path, |
@@ -29,7 +29,59 b' def extract_zip_to_directory(source: pat' | |||||
29 | zf.extractall(dest) |
|
29 | zf.extractall(dest) | |
30 |
|
30 | |||
31 |
|
31 | |||
32 |
def find_vc_runtime_ |
|
32 | def find_vc_runtime_dll(x64=False): | |
|
33 | """Finds Visual C++ Runtime DLL to include in distribution.""" | |||
|
34 | # We invoke vswhere to find the latest Visual Studio install. | |||
|
35 | vswhere = ( | |||
|
36 | pathlib.Path(os.environ["ProgramFiles(x86)"]) | |||
|
37 | / "Microsoft Visual Studio" | |||
|
38 | / "Installer" | |||
|
39 | / "vswhere.exe" | |||
|
40 | ) | |||
|
41 | ||||
|
42 | if not vswhere.exists(): | |||
|
43 | raise Exception( | |||
|
44 | "could not find vswhere.exe: %s does not exist" % vswhere | |||
|
45 | ) | |||
|
46 | ||||
|
47 | args = [ | |||
|
48 | str(vswhere), | |||
|
49 | # -products * is necessary to return results from Build Tools | |||
|
50 | # (as opposed to full IDE installs). | |||
|
51 | "-products", | |||
|
52 | "*", | |||
|
53 | "-requires", | |||
|
54 | "Microsoft.VisualCpp.Redist.14.Latest", | |||
|
55 | "-latest", | |||
|
56 | "-property", | |||
|
57 | "installationPath", | |||
|
58 | ] | |||
|
59 | ||||
|
60 | vs_install_path = pathlib.Path( | |||
|
61 | os.fsdecode(subprocess.check_output(args).strip()) | |||
|
62 | ) | |||
|
63 | ||||
|
64 | # This just gets us a path like | |||
|
65 | # C:\Program Files (x86)\Microsoft Visual Studio\2019\Community | |||
|
66 | # Actually vcruntime140.dll is under a path like: | |||
|
67 | # VC\Redist\MSVC\<version>\<arch>\Microsoft.VC14<X>.CRT\vcruntime140.dll. | |||
|
68 | ||||
|
69 | arch = "x64" if x64 else "x86" | |||
|
70 | ||||
|
71 | search_glob = ( | |||
|
72 | r"%s\VC\Redist\MSVC\*\%s\Microsoft.VC14*.CRT\vcruntime140.dll" | |||
|
73 | % (vs_install_path, arch) | |||
|
74 | ) | |||
|
75 | ||||
|
76 | candidates = glob.glob(search_glob, recursive=True) | |||
|
77 | ||||
|
78 | for candidate in reversed(candidates): | |||
|
79 | return pathlib.Path(candidate) | |||
|
80 | ||||
|
81 | raise Exception("could not find vcruntime140.dll") | |||
|
82 | ||||
|
83 | ||||
|
84 | def find_legacy_vc_runtime_files(x64=False): | |||
33 | """Finds Visual C++ Runtime DLLs to include in distribution.""" |
|
85 | """Finds Visual C++ Runtime DLLs to include in distribution.""" | |
34 | winsxs = pathlib.Path(os.environ['SYSTEMROOT']) / 'WinSxS' |
|
86 | winsxs = pathlib.Path(os.environ['SYSTEMROOT']) / 'WinSxS' | |
35 |
|
87 |
@@ -1,13 +1,24 b'' | |||||
1 | ROOT = CWD + "/../.." |
|
1 | ROOT = CWD + "/../.." | |
2 |
|
2 | |||
3 | def make_exe(): |
|
3 | # Code to run in Python interpreter. | |
4 | dist = default_python_distribution() |
|
4 | RUN_CODE = "import hgdemandimport; hgdemandimport.enable(); from mercurial import dispatch; dispatch.run()" | |
|
5 | ||||
|
6 | ||||
|
7 | set_build_path(ROOT + "/build/pyoxidizer") | |||
|
8 | ||||
5 |
|
9 | |||
6 | code = "import hgdemandimport; hgdemandimport.enable(); from mercurial import dispatch; dispatch.run()" |
|
10 | def make_distribution(): | |
|
11 | return default_python_distribution() | |||
|
12 | ||||
7 |
|
13 | |||
|
14 | def make_distribution_windows(): | |||
|
15 | return default_python_distribution(flavor="standalone_dynamic") | |||
|
16 | ||||
|
17 | ||||
|
18 | def make_exe(dist): | |||
8 | config = PythonInterpreterConfig( |
|
19 | config = PythonInterpreterConfig( | |
9 | raw_allocator = "system", |
|
20 | raw_allocator = "system", | |
10 |
run_eval = |
|
21 | run_eval = RUN_CODE, | |
11 | # We want to let the user load extensions from the file system |
|
22 | # We want to let the user load extensions from the file system | |
12 | filesystem_importer = True, |
|
23 | filesystem_importer = True, | |
13 | # We need this to make resourceutil happy, since it looks for sys.frozen. |
|
24 | # We need this to make resourceutil happy, since it looks for sys.frozen. | |
@@ -24,30 +35,65 b' def make_exe():' | |||||
24 | extension_module_filter = "all", |
|
35 | extension_module_filter = "all", | |
25 | ) |
|
36 | ) | |
26 |
|
37 | |||
27 | exe.add_python_resources(dist.pip_install([ROOT])) |
|
38 | # Add Mercurial to resources. | |
|
39 | for resource in dist.pip_install(["--verbose", ROOT]): | |||
|
40 | # This is a bit wonky and worth explaining. | |||
|
41 | # | |||
|
42 | # Various parts of Mercurial don't yet support loading package | |||
|
43 | # resources via the ResourceReader interface. Or, not having | |||
|
44 | # file-based resources would be too inconvenient for users. | |||
|
45 | # | |||
|
46 | # So, for package resources, we package them both in the | |||
|
47 | # filesystem as well as in memory. If both are defined, | |||
|
48 | # PyOxidizer will prefer the in-memory location. So even | |||
|
49 | # if the filesystem file isn't packaged in the location | |||
|
50 | # specified here, we should never encounter an errors as the | |||
|
51 | # resource will always be available in memory. | |||
|
52 | if type(resource) == "PythonPackageResource": | |||
|
53 | exe.add_filesystem_relative_python_resource(".", resource) | |||
|
54 | exe.add_in_memory_python_resource(resource) | |||
|
55 | else: | |||
|
56 | exe.add_python_resource(resource) | |||
|
57 | ||||
|
58 | # On Windows, we install extra packages for convenience. | |||
|
59 | if "windows" in BUILD_TARGET_TRIPLE: | |||
|
60 | exe.add_python_resources( | |||
|
61 | dist.pip_install(["-r", ROOT + "/contrib/packaging/requirements_win32.txt"]) | |||
|
62 | ) | |||
28 |
|
63 | |||
29 | return exe |
|
64 | return exe | |
30 |
|
65 | |||
31 | def make_install(exe): |
|
66 | ||
|
67 | def make_manifest(dist, exe): | |||
32 | m = FileManifest() |
|
68 | m = FileManifest() | |
33 |
|
||||
34 | # `hg` goes in root directory. |
|
|||
35 | m.add_python_resource(".", exe) |
|
69 | m.add_python_resource(".", exe) | |
36 |
|
70 | |||
37 | templates = glob( |
|
71 | return m | |
38 | include = [ROOT + "/mercurial/templates/**/*"], |
|
|||
39 | strip_prefix = ROOT + "/mercurial/", |
|
|||
40 | ) |
|
|||
41 | m.add_manifest(templates) |
|
|||
42 |
|
72 | |||
43 | return m |
|
|||
44 |
|
73 | |||
45 | def make_embedded_resources(exe): |
|
74 | def make_embedded_resources(exe): | |
46 | return exe.to_embedded_resources() |
|
75 | return exe.to_embedded_resources() | |
47 |
|
76 | |||
48 | register_target("exe", make_exe) |
|
77 | ||
49 | register_target("app", make_install, depends = ["exe"], default = True) |
|
78 | register_target("distribution_posix", make_distribution) | |
50 | register_target("embedded", make_embedded_resources, depends = ["exe"], default_build_script = True) |
|
79 | register_target("distribution_windows", make_distribution_windows) | |
|
80 | ||||
|
81 | register_target("exe_posix", make_exe, depends = ["distribution_posix"]) | |||
|
82 | register_target("exe_windows", make_exe, depends = ["distribution_windows"]) | |||
|
83 | ||||
|
84 | register_target( | |||
|
85 | "app_posix", | |||
|
86 | make_manifest, | |||
|
87 | depends = ["distribution_posix", "exe_posix"], | |||
|
88 | default = "windows" not in BUILD_TARGET_TRIPLE, | |||
|
89 | ) | |||
|
90 | register_target( | |||
|
91 | "app_windows", | |||
|
92 | make_manifest, | |||
|
93 | depends = ["distribution_windows", "exe_windows"], | |||
|
94 | default = "windows" in BUILD_TARGET_TRIPLE, | |||
|
95 | ) | |||
|
96 | ||||
51 | resolve_targets() |
|
97 | resolve_targets() | |
52 |
|
98 | |||
53 | # END OF COMMON USER-ADJUSTED SETTINGS. |
|
99 | # END OF COMMON USER-ADJUSTED SETTINGS. | |
@@ -55,5 +101,4 b' resolve_targets()' | |||||
55 | # Everything below this is typically managed by PyOxidizer and doesn't need |
|
101 | # Everything below this is typically managed by PyOxidizer and doesn't need | |
56 | # to be updated by people. |
|
102 | # to be updated by people. | |
57 |
|
103 | |||
58 |
PYOXIDIZER_VERSION = "0.7.0 |
|
104 | PYOXIDIZER_VERSION = "0.7.0" | |
59 | PYOXIDIZER_COMMIT = "c772a1379c3026314eda1c8ea244b86c0658951d" |
|
@@ -27,6 +27,7 b' New errors are not allowed. Warnings are' | |||||
27 | Skipping contrib/packaging/hgpackaging/downloads.py it has no-che?k-code (glob) |
|
27 | Skipping contrib/packaging/hgpackaging/downloads.py it has no-che?k-code (glob) | |
28 | Skipping contrib/packaging/hgpackaging/inno.py it has no-che?k-code (glob) |
|
28 | Skipping contrib/packaging/hgpackaging/inno.py it has no-che?k-code (glob) | |
29 | Skipping contrib/packaging/hgpackaging/py2exe.py it has no-che?k-code (glob) |
|
29 | Skipping contrib/packaging/hgpackaging/py2exe.py it has no-che?k-code (glob) | |
|
30 | Skipping contrib/packaging/hgpackaging/pyoxidizer.py it has no-che?k-code (glob) | |||
30 | Skipping contrib/packaging/hgpackaging/util.py it has no-che?k-code (glob) |
|
31 | Skipping contrib/packaging/hgpackaging/util.py it has no-che?k-code (glob) | |
31 | Skipping contrib/packaging/hgpackaging/wix.py it has no-che?k-code (glob) |
|
32 | Skipping contrib/packaging/hgpackaging/wix.py it has no-che?k-code (glob) | |
32 | Skipping i18n/polib.py it has no-che?k-code (glob) |
|
33 | Skipping i18n/polib.py it has no-che?k-code (glob) |
General Comments 0
You need to be logged in to leave comments.
Login now