First version

This commit is contained in:
andy committed 2026-10-15 20:44:05 -03:00
commit 243e92a9dd
84 files changed
+4012

No files matched your search

+296
View File
@@ -0,0 +1,296 @@
#!/usr/bin/python3
"""Install contest compiler defaults for the installed IDEs and clean team homes."""
import configparser
import io
import json
import os
from pathlib import Path
import pwd
import subprocess
import xml.etree.ElementTree as ET
changed = 0
JAVA_HOME = '/usr/lib/jvm/java-25-openjdk-amd64'
JAVA_OPTIONS = '-Dfile.encoding=UTF-8 -XX:+UseSerialGC -Xss64m -XX:TieredStopAtLevel=1 -XX:ActiveProcessorCount=2 -Xms2048m -Xmx2048m'
def write(path, text):
global changed
path = Path(path)
if not path.exists() or path.read_text() != text:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text)
changed += 1
def write_json(path, data):
write(path, json.dumps(data, indent=2) + '\n')
def read_json(path, default):
return json.loads(path.read_text()) if path.exists() else default
def geany(language, commands):
path = Path('/usr/share/geany/filedefs/filetypes.' + language)
conf = configparser.ConfigParser(interpolation=None, strict=False)
conf.optionxform = str
conf.read(path)
if not conf.has_section('build-menu'):
conf.add_section('build-menu')
for key, value in commands.items():
conf.set('build-menu', key, value)
stream = io.StringIO()
conf.write(stream, space_around_delimiters=False)
write(path, stream.getvalue())
for lang, cc in [('c', 'acpc-gcc'), ('cpp', 'acpc-g++')]:
geany(lang, {'FT_00_LB': '_Compile', 'FT_00_CM': cc + ' -c "%f" -o "%e.o"',
'FT_00_WD': '', 'FT_01_LB': '_Build', 'FT_01_CM': cc + ' "%f" -o "%e"',
'FT_01_WD': '', 'EX_00_LB': '_Execute', 'EX_00_CM': '"./%e"', 'EX_00_WD': ''})
geany('java', {'FT_00_LB': '_Compile', 'FT_00_CM': 'compilejava "%f"', 'FT_00_WD': '',
'FT_01_LB': '_Build', 'FT_01_CM': 'compilejava "%f"', 'FT_01_WD': '',
'EX_00_LB': '_Execute', 'EX_00_CM': 'runjava "%e"', 'EX_00_WD': ''})
geany('python', {'FT_00_LB': '_Compile', 'FT_00_CM': 'pypy3 -m py_compile "%f"', 'FT_00_WD': '',
'FT_01_LB': '_Build', 'FT_01_CM': 'pypy3 -m py_compile "%f"', 'FT_01_WD': '',
'EX_00_LB': '_Execute', 'EX_00_CM': 'pypy3 "%f"', 'EX_00_WD': ''})
def child(parent, tag):
node = parent.find(tag)
return node if node is not None else ET.SubElement(parent, tag)
def string(parent, tag, value):
child(child(parent, tag), 'str').text = value
def component(parent, name):
for node in parent.findall('component'):
if node.get('name') == name:
return node
return ET.SubElement(parent, 'component', name=name)
def option(parent, name, value):
node = next((n for n in parent.findall('option') if n.get('name') == name), None)
if node is None:
node = ET.SubElement(parent, 'option', name=name)
node.set('value', value)
def write_xml(path, root):
ET.indent(root)
write(path, ET.tostring(root, encoding='unicode') + '\n')
def preferences(path, values):
"""Update Java .properties keys, preserving unrelated workspace choices."""
data = {}
if path.exists():
for line in path.read_text().splitlines():
if '=' in line and not line.startswith('#'):
key, value = line.split('=', 1)
data[key] = value
for key, value in {'eclipse.preferences.version': '1', **values}.items():
data[key] = str(value).replace('\\', '\\\\').replace('\n', r'\n').replace('=', r'\=')
write(path, ''.join(k + '=' + v + '\n' for k, v in sorted(data.items())))
# Ask PyDev's own probe for the library paths of the installed PyPy.
probe = next(Path('/opt/eclipse/plugins').glob('org.python.pydev.core_*/pysrc/interpreterInfo.py'))
pyinfo = ET.fromstring(subprocess.check_output(['/usr/local/bin/pypy3', str(probe)], text=True))
ET.SubElement(pyinfo, 'name').text = 'ACPC PyPy 3.11'
ET.SubElement(pyinfo, 'vmArgs').text = ''
for lib in pyinfo.findall('lib'):
lib.attrib.clear()
pyinfo_text = ET.tostring(pyinfo, encoding='unicode') + '&&&&&'
for home in (Path('/home/team'), Path('/etc/skel')):
# Code::Blocks uses these settings for both single files and new projects.
cb_path = home / '.config/codeblocks/default.conf'
cb = ET.fromstring(cb_path.read_text()) if cb_path.exists() else ET.Element('CodeBlocksConfig', version='1')
compiler = child(cb, 'compiler')
string(compiler, 'SETTINGS_VERSION', '0.0.3')
string(compiler, 'DEFAULT_COMPILER', 'gcc')
gcc = child(child(compiler, 'sets'), 'gcc')
# Code::Blocks prepends MASTER_PATH/bin even to an absolute compiler name.
for key, value in {'NAME': 'ACPC GCC 15', 'MASTER_PATH': '/usr/local',
'EXTRA_PATHS': '/usr/bin',
'C_COMPILER': 'acpc-gcc', 'CPP_COMPILER': 'acpc-g++',
'LINKER': 'acpc-g++', 'MAKE': 'make', 'LIB_LINKER': 'ar'}.items():
string(gcc, key, value)
ET.indent(cb)
write(cb_path, '<?xml version="1.0" encoding="UTF-8"?>\n' + ET.tostring(cb, encoding='unicode') + '\n')
code = home / '.config/Code/User'
settings = read_json(code / 'settings.json', {})
settings.update({
'python.defaultInterpreterPath': '/usr/local/bin/pypy3',
'C_Cpp.default.compilerPath': '/usr/bin/g++-15',
'C_Cpp.default.cStandard': 'c23', 'C_Cpp.default.cppStandard': 'c++23',
'java.jdt.ls.java.home': JAVA_HOME,
'java.configuration.runtimes': [{'name': 'JavaSE-25', 'path': JAVA_HOME, 'default': True}],
'java.debug.settings.vmArgs': JAVA_OPTIONS,
'debug.onTaskErrors': 'abort',
})
write_json(code / 'settings.json', settings)
tasks = read_json(code / 'tasks.json', {'version': '2.0.0', 'tasks': []})
tasks['tasks'] = [t for t in tasks['tasks'] if not t.get('label', '').startswith('ACPC:')]
for action in ('build', 'run'):
task = {'label': 'ACPC: ' + ('Build' if action == 'build' else 'Build and run'),
'type': 'process', 'command': '/usr/local/bin/acpc-solution',
'args': [action, '${file}'], 'options': {'cwd': '${fileDirname}'},
'problemMatcher': ['$gcc'],
'presentation': {'reveal': 'always', 'focus': True, 'panel': 'shared', 'clear': True}}
if action == 'build': task['group'] = {'kind': 'build', 'isDefault': True}
tasks['tasks'].append(task)
write_json(code / 'tasks.json', tasks)
bindings = read_json(code / 'keybindings.json', [])
bindings = [b for b in bindings if not (b.get('key') == 'f6' or str(b.get('args', '')).startswith('ACPC:'))]
bindings.append({'key': 'f6', 'command': 'workbench.action.tasks.runTask',
'args': 'ACPC: Build and run', 'when': 'editorTextFocus'})
write_json(code / 'keybindings.json', bindings)
(home / 'contest').mkdir(exist_ok=True)
project_path = home / 'contest/.project'
if not project_path.exists():
write(project_path, '<projectDescription><name>contest</name><comment>ACPC solutions</comment><projects/><buildSpec/><natures/></projectDescription>\n')
preferences(home / 'contest/.settings/org.eclipse.core.resources.prefs', {'encoding/<project>': 'UTF-8'})
workspace = home / 'eclipse-workspace'
prefs = workspace / '.metadata/.plugins/org.eclipse.core.runtime/.settings'
preferences(prefs / 'org.python.pydev.prefs', {
'INTERPRETER_PATH_NEW': pyinfo_text,
# PyDev's first-editor prompt would disable automatic Java/CDT builds
# across this mixed-language workspace. Keep those defaults and skip it.
'CHECK_PREFERRED_PYDEV_SETTINGS': 'false',
})
preferences(prefs / 'org.eclipse.jdt.core.prefs', {
'org.eclipse.jdt.core.compiler.compliance': '25',
'org.eclipse.jdt.core.compiler.source': '25',
'org.eclipse.jdt.core.compiler.codegen.targetPlatform': '25',
})
vm_type = 'org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType'
vm_id = 'acpc-java-25'
vms = ET.Element('vmSettings', defaultVM=f'{len(vm_type)},{vm_type}{len(vm_id)},{vm_id}', defaultVMConnector='')
vmtype = ET.SubElement(vms, 'vmType', id=vm_type)
ET.SubElement(vmtype, 'vm', id=vm_id, name='ACPC Java 25', path=JAVA_HOME, vmArgs=JAVA_OPTIONS)
preferences(prefs / 'org.eclipse.jdt.launching.prefs', {'org.eclipse.jdt.launching.PREF_VM_XML': ET.tostring(vms, encoding='unicode')})
preferences(prefs / 'org.eclipse.ui.prefs', {'showIntro': 'false'})
preferences(prefs / 'org.eclipse.core.resources.prefs', {'encoding': 'UTF-8'})
for action in ('build', 'run'):
title = 'ACPC ' + ('Build' if action == 'build' else 'Build and run')
launch = ET.Element('launchConfiguration', type='org.eclipse.ui.externaltools.ProgramLaunchConfigurationType')
for key, value in {
'org.eclipse.ui.externaltools.ATTR_LOCATION': '/usr/local/bin/acpc-solution',
'org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS': action + ' "${resource_loc}"',
'org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY': '${container_loc}',
'org.eclipse.debug.core.ATTR_REFRESH_SCOPE': '${workspace}',
}.items():
ET.SubElement(launch, 'stringAttribute', key=key, value=value)
for key, value in {
'org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND': 'true',
'org.eclipse.debug.core.ATTR_CAPTURE_OUTPUT': 'true',
'org.eclipse.debug.ui.ATTR_CONSOLE_OUTPUT_ON': 'true',
'org.eclipse.debug.ui.ATTR_CONSOLE_ERROR_ON': 'true',
}.items():
ET.SubElement(launch, 'booleanAttribute', key=key, value=value)
favorites = ET.SubElement(launch, 'listAttribute', key='org.eclipse.debug.ui.favoriteGroups')
ET.SubElement(favorites, 'listEntry', value='org.eclipse.ui.externaltools.launchGroup')
write_xml(workspace / '.metadata/.plugins/org.eclipse.debug.core/.launches' / (title + '.launch'), launch)
history_path = workspace / '.metadata/.plugins/org.eclipse.debug.ui/launchConfigurationHistory.xml'
history = ET.fromstring(history_path.read_text()) if history_path.exists() else ET.Element('launchHistory')
group_id = 'org.eclipse.ui.externaltools.launchGroup'
group = next((g for g in history if g.get('id') == group_id), None)
if group is None:
group = ET.SubElement(history, 'launchGroup', id=group_id)
child(group, 'mruHistory')
favorites = child(group, 'favorites')
for title in ('ACPC Build and run', 'ACPC Build'):
if not any(title == ET.fromstring(n.get('memento', '<x/>')).get('path') for n in favorites):
ET.SubElement(favorites, 'launch', memento=ET.tostring(ET.Element('launchConfiguration', local='true', path=title), encoding='unicode'))
write_xml(history_path, history)
for ide in ('clion', 'intellij-idea', 'pycharm'):
info = Path('/opt') / ide / 'product-info.json'
if not info.exists():
continue
options = home / '.config/JetBrains' / json.loads(info.read_text())['dataDirectoryName'] / 'options'
default_file = options / 'project.default.xml'
defaults = ET.fromstring(default_file.read_text()) if default_file.exists() else ET.Element('application')
project = child(component(defaults, 'ProjectManager'), 'defaultProject')
runs = component(project, 'RunManager')
if ide in ('pycharm', 'clion'):
sdk_file = options / 'jdk.table.xml'
sdks = ET.fromstring(sdk_file.read_text()) if sdk_file.exists() else ET.Element('application')
table = component(sdks, 'ProjectJdkTable')
sdk_name = 'PyPy 3.11 (as on Kattis)'
for node in list(table):
if node.find('name') is not None and node.find('name').get('value') == sdk_name:
table.remove(node)
sdk = ET.SubElement(table, 'jdk', version='2')
for key, value in [('name', sdk_name), ('type', 'Python SDK'),
('version', 'Python 3.11.15'), ('homePath', '/usr/local/bin/pypy3')]:
ET.SubElement(sdk, key, value=value)
roots = ET.SubElement(sdk, 'roots')
cp = ET.SubElement(ET.SubElement(roots, 'classPath'), 'root', type='composite')
for lib in pyinfo.findall('lib'):
ET.SubElement(cp, 'root', url='file://' + lib.text, type='simple')
ET.SubElement(ET.SubElement(roots, 'sourcePath'), 'root', type='composite')
additional = ET.SubElement(sdk, 'additional', SDK_UUID='3f7d6c1e-2a4b-4c8d-9e0f-a1b2c3d4e5f6')
ET.SubElement(additional, 'setting', name='FLAVOR_ID', value='PyPySdkFlavor')
ET.SubElement(additional, 'setting', name='FLAVOR_DATA', value='{}')
write_xml(sdk_file, sdks)
root_manager = component(project, 'ProjectRootManager')
root_manager.attrib.update({'version': '2', 'project-jdk-name': sdk_name, 'project-jdk-type': 'Python SDK'})
if ide == 'clion':
chain_file = options / 'linux/toolchains.xml'
chains = ET.fromstring(chain_file.read_text()) if chain_file.exists() else ET.Element('application')
cpp = component(chains, 'CPPToolchains')
cpp.set('version', '10')
entries = child(cpp, 'toolchains')
entries.set('detectedVersion', '5')
chain = entries.find('toolchain')
if chain is None:
chain = ET.SubElement(entries, 'toolchain', name='Default', toolSetKind='SYSTEM_UNIX_TOOLSET', debuggerKind='BUNDLED_GDB')
chain.set('customCCompilerPath', '/usr/local/bin/acpc-gcc')
chain.set('customCXXCompilerPath', '/usr/local/bin/acpc-g++')
write_xml(chain_file, chains)
for node in list(runs):
if node.get('default') == 'true' and node.get('type') == 'CppFileRunConfiguration':
runs.remove(node)
cfg = ET.SubElement(runs, 'configuration', default='true', type='CppFileRunConfiguration', factoryName='CppFileRunConfiguration')
option(cfg, 'compilerFile', '/usr/local/bin/acpc-cc')
option(cfg, 'compilerOptions', '')
if ide == 'intellij-idea':
sdk_file = options / 'jdk.table.xml'
sdks = ET.fromstring(sdk_file.read_text()) if sdk_file.exists() else ET.Element('application')
table = component(sdks, 'ProjectJdkTable')
for node in list(table):
if node.find('name') is not None and node.find('name').get('value') == 'ACPC Java 25':
table.remove(node)
sdk = ET.SubElement(table, 'jdk', version='2')
for key, value in [('name', 'ACPC Java 25'), ('type', 'JavaSDK'),
('version', 'java version "25.0.4.1"'), ('homePath', JAVA_HOME)]:
ET.SubElement(sdk, key, value=value)
roots = ET.SubElement(sdk, 'roots')
cp = ET.SubElement(ET.SubElement(roots, 'classPath'), 'root', type='composite')
for module in sorted(Path(JAVA_HOME, 'jmods').glob('*.jmod')):
ET.SubElement(cp, 'root', url=f'jrt://{JAVA_HOME}!/{module.stem}', type='simple')
ET.SubElement(ET.SubElement(roots, 'sourcePath'), 'root', type='composite')
ET.SubElement(sdk, 'additional')
write_xml(sdk_file, sdks)
root_manager = component(project, 'ProjectRootManager')
root_manager.attrib.update({'version': '2', 'languageLevel': 'JDK_25',
'project-jdk-name': 'ACPC Java 25', 'project-jdk-type': 'JavaSDK'})
for kind, factory in [('Application', 'Application'), ('JavaScratch', 'Java Scratch')]:
for node in list(runs):
if node.get('default') == 'true' and node.get('type') == kind:
runs.remove(node)
cfg = ET.SubElement(runs, 'configuration', default='true', type=kind, factoryName=factory)
option(cfg, 'VM_PARAMETERS', JAVA_OPTIONS)
option(cfg, 'ALTERNATIVE_JRE_PATH', JAVA_HOME)
option(cfg, 'ALTERNATIVE_JRE_PATH_ENABLED', 'true')
encoding = component(project, 'Encoding')
for node in list(encoding):
if node.get('url') == 'PROJECT':
encoding.remove(node)
ET.SubElement(encoding, 'file', url='PROJECT', charset='UTF-8')
write_xml(default_file, defaults)
if home == Path('/home/team'):
account = pwd.getpwnam('team')
for folder in (home / '.config/codeblocks', code, home / 'contest', home / '.config/JetBrains', workspace):
for path in [folder, *folder.rglob('*')]:
os.chown(path, account.pw_uid, account.pw_gid)
print(f'changed={changed}')
+46
View File
@@ -0,0 +1,46 @@
#!/bin/bash
# Usage: jetbrains_first_run.sh HOME_DIR
# Settings of the JetBrains IDEs (CLion, IntelliJ IDEA, PyCharm) for the team
# account, so that the teams go straight to work:
# - user agreement accepted, no usage data sharing (first-launch questions)
# - the home directory /home/team trusted (no "Trust project?" question)
# Compiler and interpreter defaults are installed by configure_ides.py.
# The folder name under .java is how Java stores the preference node
# "privacy_policy".
set -eu
dest_home=$1
prefs="$dest_home/.java/.userPrefs/jetbrains/"$'_!(!!cg"p!(}!}@"j!(k!|w"w!\'8!b!"p!\':!e@=='
consent="$dest_home/.local/share/JetBrains/consentOptions"
mkdir -p "$prefs" "$consent"
cat > "$prefs/prefs.xml" <<'XML'
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE map SYSTEM "http://java.sun.com/dtd/preferences.dtd">
<map MAP_XML_VERSION="1.0">
<entry key="eua_accepted_version" value="2.0"/>
</map>
XML
printf 'rsch.send.usage.stat:1.1:0:1791991599000' > "$consent/accepted"
for ide in pycharm intellij-idea clion; do
[ -f "/opt/$ide/product-info.json" ] || continue
dir=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["dataDirectoryName"])' "/opt/$ide/product-info.json")
options="$dest_home/.config/JetBrains/$dir/options"
mkdir -p "$options"
cat > "$options/trusted-paths.xml" <<'XML'
<application>
<component name="Trusted.Paths.Settings">
<option name="TRUSTED_PATHS">
<list>
<option value="/home/team" />
</list>
</option>
</component>
</application>
XML
done
if [ "$dest_home" != /etc/skel ]; then
owner=$(stat -c %U:%G "$dest_home")
chown -R "$owner" "$dest_home/.java" "$dest_home/.local" "$dest_home/.config/JetBrains"
fi