First version
This commit is contained in:
commit
243e92a9dd
84 files changed
+4012
No files matched your search
@@ -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}')
|
||||
Executable
+46
@@ -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
|
||||
Reference in new issue
Block a user