mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-03 15:49:07 -04:00
343 lines
10 KiB
Python
343 lines
10 KiB
Python
# interactive_port.py
|
|
#
|
|
# Copyright 2021 James Westman <james@jwestman.net>
|
|
#
|
|
# This file is free software; you can redistribute it and/or modify it
|
|
# under the terms of the GNU Lesser General Public License as
|
|
# published by the Free Software Foundation; either version 3 of the
|
|
# License, or (at your option) any later version.
|
|
#
|
|
# This file is distributed in the hope that it will be useful, but
|
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
# Lesser General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Lesser General Public
|
|
# License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
|
|
|
|
import difflib
|
|
import os
|
|
import typing as T
|
|
|
|
from . import decompiler, parser, tokenizer
|
|
from .errors import CompilerBugError, MultipleErrors, PrintableError
|
|
from .outputs.xml import XmlOutput
|
|
from .utils import Colors
|
|
|
|
# A tool to interactively port projects to blueprints.
|
|
|
|
|
|
class CouldNotPort:
|
|
def __init__(self, message: str):
|
|
self.message = message
|
|
|
|
|
|
def change_suffix(f):
|
|
return f.removesuffix(".ui") + ".blp"
|
|
|
|
|
|
def decompile_file(in_file, out_file) -> T.Union[str, CouldNotPort]:
|
|
if os.path.exists(out_file):
|
|
return CouldNotPort("already exists")
|
|
|
|
try:
|
|
decompiled = decompiler.decompile(in_file)
|
|
|
|
try:
|
|
# make sure the output compiles
|
|
tokens = tokenizer.tokenize(decompiled)
|
|
ast, errors, warnings = parser.parse(tokens)
|
|
|
|
for warning in warnings:
|
|
warning.pretty_print(out_file, decompiled)
|
|
|
|
if errors:
|
|
raise errors
|
|
if not ast:
|
|
raise CompilerBugError()
|
|
|
|
output = XmlOutput()
|
|
output.emit(ast)
|
|
except PrintableError as e:
|
|
e.pretty_print(out_file, decompiled)
|
|
|
|
print(
|
|
f"{Colors.RED}{Colors.BOLD}error: the generated file does not compile{Colors.CLEAR}"
|
|
)
|
|
print(f"in {Colors.UNDERLINE}{out_file}{Colors.NO_UNDERLINE}")
|
|
print(
|
|
f"""{Colors.FAINT}Either the original XML file had an error, or there is a bug in the
|
|
porting tool. If you think it's a bug (which is likely), please file an issue on GitLab:
|
|
{Colors.BLUE}{Colors.UNDERLINE}https://gitlab.gnome.org/jwestman/blueprint-compiler/-/issues/new?issue{Colors.CLEAR}\n"""
|
|
)
|
|
|
|
return CouldNotPort("does not compile")
|
|
|
|
return decompiled
|
|
|
|
except decompiler.UnsupportedError as e:
|
|
e.print(in_file)
|
|
return CouldNotPort("could not convert")
|
|
|
|
|
|
def listdir_recursive(subdir):
|
|
files = os.listdir(subdir)
|
|
for file in files:
|
|
if file in ["_build", "build"]:
|
|
continue
|
|
if file.startswith("."):
|
|
continue
|
|
full = os.path.join(subdir, file)
|
|
if full == "./subprojects":
|
|
# skip the subprojects directory
|
|
continue
|
|
if os.path.isfile(full):
|
|
yield full
|
|
elif os.path.isdir(full):
|
|
yield from listdir_recursive(full)
|
|
|
|
|
|
def yesno(prompt):
|
|
while True:
|
|
response = input(f"{Colors.BOLD}{prompt} [y/n] {Colors.CLEAR}")
|
|
if response.lower() in ["yes", "y"]:
|
|
return True
|
|
elif response.lower() in ["no", "n"]:
|
|
return False
|
|
|
|
|
|
def enter():
|
|
input(f"{Colors.BOLD}Press Enter when you have done that: {Colors.CLEAR}")
|
|
|
|
|
|
def step1():
|
|
print(
|
|
f"{Colors.BOLD}STEP 1: Create subprojects/blueprint-compiler.wrap{Colors.CLEAR}"
|
|
)
|
|
|
|
if os.path.exists("subprojects/blueprint-compiler.wrap"):
|
|
print("subprojects/blueprint-compiler.wrap already exists, skipping\n")
|
|
return
|
|
|
|
if yesno("Create subprojects/blueprint-compiler.wrap?"):
|
|
try:
|
|
os.mkdir("subprojects")
|
|
except:
|
|
pass
|
|
|
|
from .main import VERSION
|
|
|
|
VERSION = "main" if VERSION == "uninstalled" else "v" + VERSION
|
|
|
|
with open("subprojects/blueprint-compiler.wrap", "w") as wrap:
|
|
wrap.write(
|
|
f"""[wrap-git]
|
|
directory = blueprint-compiler
|
|
url = https://gitlab.gnome.org/jwestman/blueprint-compiler.git
|
|
revision = {VERSION}
|
|
depth = 1
|
|
|
|
[provide]
|
|
program_names = blueprint-compiler"""
|
|
)
|
|
|
|
print()
|
|
|
|
|
|
def step2():
|
|
print(f"{Colors.BOLD}STEP 2: Set up .gitignore{Colors.CLEAR}")
|
|
|
|
if os.path.exists(".gitignore"):
|
|
with open(".gitignore", "r+") as gitignore:
|
|
ignored = [line.strip() for line in gitignore.readlines()]
|
|
if "/subprojects/blueprint-compiler" not in ignored:
|
|
if yesno("Add '/subprojects/blueprint-compiler' to .gitignore?"):
|
|
gitignore.write("\n/subprojects/blueprint-compiler\n")
|
|
else:
|
|
print(
|
|
"'/subprojects/blueprint-compiler' already in .gitignore, skipping"
|
|
)
|
|
else:
|
|
if yesno("Create .gitignore with '/subprojects/blueprint-compiler'?"):
|
|
with open(".gitignore", "w") as gitignore:
|
|
gitignore.write("/subprojects/blueprint-compiler\n")
|
|
|
|
print()
|
|
|
|
|
|
def step3():
|
|
print(f"{Colors.BOLD}STEP 3: Convert UI files{Colors.CLEAR}")
|
|
|
|
files = [
|
|
(file, change_suffix(file), decompile_file(file, change_suffix(file)))
|
|
for file in listdir_recursive(".")
|
|
if file.endswith(".ui")
|
|
]
|
|
|
|
success = 0
|
|
for in_file, out_file, result in files:
|
|
if isinstance(result, CouldNotPort):
|
|
if result.message == "already exists":
|
|
print(Colors.FAINT, end="")
|
|
print(
|
|
f"{Colors.RED}will not port {Colors.UNDERLINE}{in_file}{Colors.NO_UNDERLINE} -> {Colors.UNDERLINE}{out_file}{Colors.NO_UNDERLINE} [{result.message}]{Colors.CLEAR}"
|
|
)
|
|
else:
|
|
print(
|
|
f"will port {Colors.UNDERLINE}{in_file}{Colors.CLEAR} -> {Colors.UNDERLINE}{out_file}{Colors.CLEAR}"
|
|
)
|
|
success += 1
|
|
|
|
print()
|
|
if len(files) == 0:
|
|
print(f"{Colors.RED}No UI files found.{Colors.CLEAR}")
|
|
elif success == len(files):
|
|
print(f"{Colors.GREEN}All files were converted.{Colors.CLEAR}")
|
|
elif success > 0:
|
|
print(
|
|
f"{Colors.RED}{success} file(s) were converted, {len(files) - success} were not.{Colors.CLEAR}"
|
|
)
|
|
else:
|
|
print(f"{Colors.RED}None of the files could be converted.{Colors.CLEAR}")
|
|
|
|
if success > 0 and yesno("Save these changes?"):
|
|
for in_file, out_file, result in files:
|
|
if not isinstance(result, CouldNotPort):
|
|
with open(out_file, "x") as file:
|
|
file.write(result)
|
|
|
|
print()
|
|
results = [
|
|
(in_file, out_file)
|
|
for in_file, out_file, result in files
|
|
if not isinstance(result, CouldNotPort) or result.message == "already exists"
|
|
]
|
|
if len(results):
|
|
return zip(*results)
|
|
else:
|
|
return ([], [])
|
|
|
|
|
|
def step4(ported):
|
|
print(f"{Colors.BOLD}STEP 4: Set up meson.build{Colors.CLEAR}")
|
|
print(
|
|
f"{Colors.BOLD}{Colors.YELLOW}NOTE: Depending on your build system setup, you may need to make some adjustments to this step.{Colors.CLEAR}"
|
|
)
|
|
|
|
meson_files = [
|
|
file
|
|
for file in listdir_recursive(".")
|
|
if os.path.basename(file) == "meson.build"
|
|
]
|
|
for meson_file in meson_files:
|
|
with open(meson_file, "r") as f:
|
|
if "gnome.compile_resources" in f.read():
|
|
parent = os.path.dirname(meson_file)
|
|
file_list = "\n ".join(
|
|
[
|
|
f"'{os.path.relpath(file, parent)}',"
|
|
for file in ported
|
|
if file.startswith(parent)
|
|
]
|
|
)
|
|
|
|
if len(file_list):
|
|
print(
|
|
f"{Colors.BOLD}Paste the following into {Colors.UNDERLINE}{meson_file}{Colors.NO_UNDERLINE}:{Colors.CLEAR}"
|
|
)
|
|
print(
|
|
f"""
|
|
blueprints = custom_target('blueprints',
|
|
input: files(
|
|
{file_list}
|
|
),
|
|
output: '.',
|
|
command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'],
|
|
)
|
|
"""
|
|
)
|
|
enter()
|
|
|
|
print(
|
|
f"""{Colors.BOLD}Paste the following into the 'gnome.compile_resources()'
|
|
arguments in {Colors.UNDERLINE}{meson_file}{Colors.NO_UNDERLINE}:{Colors.CLEAR}
|
|
|
|
dependencies: blueprints,
|
|
"""
|
|
)
|
|
enter()
|
|
|
|
print()
|
|
|
|
|
|
def step5(in_files):
|
|
print(f"{Colors.BOLD}STEP 5: Update POTFILES.in{Colors.CLEAR}")
|
|
|
|
if not os.path.exists("po/POTFILES.in"):
|
|
print(
|
|
f"{Colors.UNDERLINE}po/POTFILES.in{Colors.NO_UNDERLINE} does not exist, skipping\n"
|
|
)
|
|
return
|
|
|
|
with open("po/POTFILES.in", "r") as potfiles:
|
|
old_lines = potfiles.readlines()
|
|
lines = old_lines.copy()
|
|
for in_file in in_files:
|
|
for i, line in enumerate(lines):
|
|
if line.strip() == in_file.removeprefix("./"):
|
|
lines[i] = change_suffix(line.strip()) + "\n"
|
|
|
|
new_data = "".join(lines)
|
|
|
|
print(
|
|
f"{Colors.BOLD}Will make the following changes to {Colors.UNDERLINE}po/POTFILES.in{Colors.CLEAR}"
|
|
)
|
|
print(
|
|
"".join(
|
|
[
|
|
(
|
|
Colors.GREEN
|
|
if line.startswith("+")
|
|
else Colors.RED + Colors.FAINT
|
|
if line.startswith("-")
|
|
else ""
|
|
)
|
|
+ line
|
|
+ Colors.CLEAR
|
|
for line in difflib.unified_diff(old_lines, lines)
|
|
]
|
|
)
|
|
)
|
|
|
|
if yesno("Is this ok?"):
|
|
with open("po/POTFILES.in", "w") as potfiles:
|
|
potfiles.writelines(lines)
|
|
|
|
print()
|
|
|
|
|
|
def step6(in_files):
|
|
print(f"{Colors.BOLD}STEP 6: Clean up{Colors.CLEAR}")
|
|
|
|
if yesno("Delete old XML files?"):
|
|
for file in in_files:
|
|
try:
|
|
os.remove(file)
|
|
except:
|
|
pass
|
|
|
|
|
|
def run(opts):
|
|
step1()
|
|
step2()
|
|
in_files, out_files = step3()
|
|
step4(out_files)
|
|
step5(in_files)
|
|
step6(in_files)
|
|
|
|
print(
|
|
f"{Colors.BOLD}STEP 6: Done! Make sure your app still builds and runs correctly.{Colors.CLEAR}"
|
|
)
|