Imported from andrepg/jetbrains-flatpak-plugin (
AGENTS.md). Install upstream withnpx skills add andrepg/jetbrains-flatpak-plugin. Copyright stays with the author.
AGENTS.md
Plugin basics
- Plugin ID:
io.github.andrepg.flatpak-support - Target IDE: IntelliJ IDEA 2025.3.5
- Language: Kotlin
- Build system: Gradle with IntelliJ Platform Gradle Plugin
Key files
- Entry point:
src/main/resources/META-INF/plugin.xml - Flatpak runs:
src/main/kotlin/io/github/andrepg/flatpak/runs/UserVisibleCommand.kt: Command enums (InternalCommand: BUILD, EXPORT, RUN, VALIDATE, CUSTOM;UserVisibleCommand— same set, what the run-config editor shows)configuration/: Run configuration machinery (type, configuration, factory, generator, manifest producer, project opener, settings defaults).FlatpakRunGenerator.formatRunName(command, appId)names configs[build] <app-id>;RunManifestProducer.setupConfigurationFromContextsets command/manifest/buildDir/name explicitly.FlatpakRunSettingsimplementsLocatableConfiguration:suggestedName()drives the Run → Edit Configurations → New name ([command] <app-id>),isGeneratedName()recognizes the generated pattern;RunConfigurationValidator(pure JDK) collects errors forcheckConfigurationcommands/: everything that renders a Flatpak CLI line.CommandExecutionStrategy.kt: maps the selectedUserVisibleCommandto theInternalCommandexecutedcommands/CommandExecutionEngine.kt: Maps one command to a flatpak-builder/flatpak CLI line;toGeneralCommandLinefor the IDE process API; wraps command/process failures inFlatpakExecutionException. Constructor takes ahostBusAvailablepredicate (defaultCommandExecutionArguments::hostHasFlatpakBus) so RUN's D-Bus sockets are testablecommands/CommandExecutionArguments.kt: sandbox flag sets —DEFAULT_BUS(--socket=session-bus,--socket=system-bus, added toRunonly whenhostHasFlatpakBus()—/run/flatpak/busexists; otherwise skipped with a warning, GNOME Builder-style filtered default bus) + opt-in portals/themes/audio/waylandcommands/CommandFactory.kt: base class withgetFlatpakCommand()/buildSandboxOptions()and theeffectiveBuildDir()/effectiveManifestPath()non-blank guards (I1); one subclass per command (Build/Run/Custom/ExportBundle/ValidateManifest)cleanup/: blocking pre-step implementations —DeepCleanExecutor.ktdeletes deep-clean targets through the IntelliJ VFS inside aWriteCommandAction(pooled-thread caller required);StaleFuseMountCleaner.kt(pure JDK) unmounts stale rofiles-fuse mounts inside flatpak-builder state dirs (project root + build dir — the state dir lives at the working directory, NOT under_build), fail-opensteps/: orchestration pipeline.CommandChainProcessHandler.kt:ProcessHandlerthat runs blocking pre-steps on a pooled thread — never the EDT — then runs the mainOSProcessHandlerand relays its output/termination. Every step is announced to the console as a named workflow step (Running DEEP_CLEAN...,Running BUILD: <cmdline>,<label> finished with exit code N) so the flow is visible, not just the build reportsteps/FlatpakRunner.kt:CommandLineStatethat picks the command, labels the chain steps, routes deep clean throughCommandChainProcessHandler, and attaches the consoleui/FlatpakRunSettingsPanel.kt: Settings editor form for the run configuration. Option groups are command-sensitive and toggle live with the command combo: cleanup for BUILD, portal permissions for RUN, Custom arguments (shown right below the command box) for CUSTOM
- Manifest reading (IO policy, §3.2): single file
flatpak/utils/FlatpakManifestReaders.ktwith two objects.FlatpakManifestReader= pure-JDK parser (parseFields(content, fileName, keys), throwsFlatpakManifestException) + forgiving JDK conveniences (readFields(path),readAppId(path); sharedparseFieldsForgiving/pickAppIdinternals) for tooling/hermetic tests.FlatpakManifestVfsReader= IDE glue readingVirtualFile(or a path viaLocalFileSystem, with guarded JDK fallback for headless tests); API:readFields(file|project,path),readAppId(file|project,path),readCommand(project,path). IDE-glue callers (FlatpakProjectDetector,RunCommandFactory,GtkSdkHintResolver) read through the VFS reader. - Exceptions:
flatpak/exception/FlatpakExceptions.kt—FlatpakPluginExceptionbase +FlatpakManifestException/FlatpakExecutionException/FlatpakConfigurationException(all pure JDK). Wrapped at boundaries (CommandExecutionEngine); platform contracts kept (RuntimeConfigurationErrorinFlatpakRunSettings.checkConfiguration). - Billing/licensing:
src/main/kotlin/io/github/andrepg/shared/license/(LicenseCheck= Kotlin port of JetBrains'CheckLicense, verifiesLicensingFacadestamps;PremiumFeatureGate= the single premium/paid-feature decision point). Freemium plugin:<product-descriptor code="PFLATPAKDEV" ... optional="true"/>inplugin.xml, product versioned2026.1.x(calendar scheme). The gate also honors the dev system propertyflatpak.devtools.development(set automatically byrunIde) so development is never locked out. SeeBILLING.md. - Diagnostics & error reporting:
shared/log/LogConfiguration.kt(JDK-only debug toggle for theio.github.andrepg.*JUL namespace),shared/diagnostics/DiagnosticsInitializer.kt(IDE glue,com.intellij.ide.AppLifecycleListener; applies the settings/system-property config at startup and logs a summary),shared/sentry/SentryInitializer.kt(DSN chain property → env → constant;reconfigure()is idempotent) +SentryLogBridge.kt(LogListenermapping Log → Sentry events/breadcrumbs; no-ops when the client is off). TheLogfacade (shared/log/Log.kt) is JDK-only;Log.listeneris a single global listener wired only when Sentry is enabled. See the Feature flags section below for the properties.
Commands
Development
./gradlew runIde # Launch sandbox IDE with plugin loaded
./gradlew build # Build plugin ZIP in build/distributions/
Do NOT run verifyPlugin or runPluginVerifier unless explicitly asked.
Compatibility checks (only when asked)
./gradlew verifyPlugin # Check plugin compatibility
./gradlew runPluginVerifier # Run IntelliJ Plugin Verifier (if configured)
Publishing
./gradlew publishPlugin # Publish to JetBrains Marketplace (requires PUBLISH_TOKEN)
Testing
./gradlew test # Run tests
Flatpak integration notes
- Commands execute via
flatpak-builderandflatpakCLI FlatpakCommand.RUNexecutes the manifest'scommandfield (read via the VFS reader inRunCommandFactory), falling back to the app-id; the deep-clean pre-step (DEEP_CLEAN) runs synchronously as aPreStepinCommandChainProcessHandlerbefore the main command- Run command sandbox includes the D-Bus sockets (
DEFAULT_BUS:--socket=session-bus,--socket=system-bus) before the opt-in portal/theme/audio/wayland flags and the positionalDIRECTORY MANIFEST COMMANDargs (I2) — but only whenCommandExecutionArguments.hostHasFlatpakBus()(/run/flatpak/busexists); otherwise the sockets are skipped with a warning and the run relies on flatpak's filtered default session bus (GNOME Builder behaves the same way) - The deep clean runs inside a
WriteCommandActionon the pooled thread (DeepCleanExecutor), never a rawrunWriteAction— the EDT would throw "Background write action is not permitted on this thread" - Factories never emit a blank
buildDir/manifestPathpositional arg:CommandFactory.effectiveBuildDir()/effectiveManifestPath()default to_build/flatpak.json(I1) - EXPORT/VALIDATE (and I5) fail inside the sandbox IDE for a documented, non-fixable-in-plugin reason:
flatpak-node-generator/pip3/pipxare missing from the Builder runtime (flatpak-builder --runmodule-lacks-python issue). The build report's export failure is a sandbox-IDE artifact, not a plugin bug — see README/CHANGELOG - Configuration requires:
manifestPath: Path to flatpak manifest fileBUILD_DIR: Build directory for flatpak-builder
GNOME/Adwaita UI support
.ui/.gladeare served a generated XSD (no target namespace, root<interface>); there is no bundled schema — the XSD is generated at runtime from the project's installed GNOME SDK and cached in the plugin config dir.- The XSD is wired through the XML plugin's
com.intellij.xml.schemaProviderEP (XmlSchemaProvider, seesrc/main/kotlin/io/github/andrepg/gtk/schema/providers/GtkInterfaceXmlSchemaProvider.kt);.ui/.gladeare mapped to the XML file type via<fileType name="XML" extensions="ui;glade"/>inplugin.xml, so the files open as XML (highlighting, structure view) and get schema completion/validation. Plain.xmlfiles are also served when their root element is<interface>(matches the schema) and the project is a recognized Flatpak project (gated ingetSchemavia theSdkHint). - Until the first successful generation (or when no SDK can be located) no schema is served; failures surface through the existing warning balloon.
- LSP integration would require additional dependencies and configuration
Configuration quirks
- Target IDE version is hardcoded in
build.gradle.kts:15 - Configuration cache enabled in
gradle.properties:8 - Kotlin stdlib opt-out in
gradle.properties:5 - Gradle build cache enabled in
gradle.properties(org.gradle.caching=true); it has served a stalecompileTestKotlinABI once after a visibility change — run local verification as./gradlew clean build --no-build-cache(CI is unaffected, fresh runner)
Agent guardrails
- No internal APIs: never use
@ApiStatus.Internalor@IntellijInternalApi-annotated platform classes (e.g.,PluginManagerCore). Prefer pure-JVM alternatives (classpath resources, reflection-free patterns). TheverifyPlugintask flags these as failures since IntelliJ Platform Gradle Plugin 2.15.0. - Save plans locally: write request plans and task breakdowns into the
plans/folder only when the user explicitly asks.
Architecture
- Plugin uses IntelliJ's
ConfigurationTypeBasefor run configurations - Flatpak commands integrate with IntelliJ's
CommandLineState - Message bundles in
src/main/resources/messages/for i18n
GTK schema namespace
- The GTK/Adwaita schema feature lives under
io.github.andrepg.gtk(not the Flatpak namespace). - Core (
gtk/schema/,gtk/schema/gir/,gtk/schema/locator/) is JDK-only (no IntelliJ imports) so it runs inside the IDE and stays unit-testable.gtk/schema/providers/is IDE glue and the composition root: it computes theSdkHintfromFlatpakManifestVfsReader.readFields(file, "sdk", "runtime")viaFlatpakProjectDetector.findManifests(), then delegates toGtkSchemaManager. GtkSchemaManagerresolves the project SDK's GIR dir viaGirSdkLocator(flatpak CLI only, no install-root fallback; discovery is gated by the curatedGirSdkLocator.supportedSdksallowlist — currentlyorg.gnome.Sdkandorg.freedesktop.Platform) and generatesgtk-ui-<key>.xsdinto the plugin config dir (idempotent, background task). This runtime generation is the only schema source — there is no bundled fallback.- The GIR→XSD pipeline is split:
gir/parser/GirParserparses the GIR files into aRegistryof types (missing optional GIR files likeGtkSource-5.girare skipped with a warning);gir/builder/XsdBuilderderives the name enums and appliesSchemaPatcheson top of the static skeletongir/builder/XsdSkeleton.RAW(whosegb-patch:*markers must matchSchemaPatches.xsdPatches);gir/GirSchemaExtractor.generateXsdorchestrates it all — callers pass the GIR dir and it fails fast withoutGtk-4.0.gir. Never run schema generation during app lifecycle outside the background scheduling inGtkInterfaceXmlSchemaProvider. - The GTK snapshot preview renders
.uifiles viagtk4-builder-toolinside the GNOME SDK:GtkBuilderToolRunner(validate/render, JDK-only) +AdwShimManager(per-branchadw_init()constructor shim compiled withcc/pkg-config, cached in the config dir). Host/tmpis masked inside the flatpak sandbox, so test/preview files must live under$HOME(exposed via--filesystem=host).
Next steps for full implementation
- Implement LSP for XML files
- Add proper configuration validation
- GResource integration and undeclared-file notifications
- Runtime GTK schema/preview polish
CI/CD
.github/workflows/ci.yml: on PR/push —./gradlew build+./gradlew test(GTK tests are@Ignore'd, so this is the non-GTK gate)..github/workflows/publish.yml: onv*tag / manual — resolves the release name (flatpak-devtools@<version>fromgradle.properties, fails on tag mismatch),verifyPlugin, creates the Sentry release viagetsentry/action-release(unfinalized + auto commits), thenpublishPlugin, then finalizes the release with aproductiondeploy record. Requires theGH_JETBRAINS_PUBLISH_TOKENandSENTRY_AUTH_TOKENsecrets (the latter also enables the Gradle plugin's source-context upload).
Feature flags (runtime system properties)
flatpak.gtk.preview.enabled— enables the GTK preview/schema premium features (also the Marketplace<with>property).flatpak.devtools.development— dev-only premium unlock, set byrunIde.flatpak.debug— plugin-wide debug logging: raises the JUL level toFINEon theio.github.andrepg.*categories (also available as Settings → Languages & Frameworks → Flatpak → Diagnostics → Debug logging). Enabled state lives inLogConfiguration(shared/log/LogConfiguration.kt).flatpak.sentry.enabled— opt-in Sentry error reporting (also the Share anonymous error reports checkbox in the Diagnostics group). When on,SentryLogBridge(aLogListener) forwardsLog.error+ throwable toSentry.captureException, plainLog.errortocaptureMessage(ERROR), and warnings to breadcrumbs.flatpak.sentry.dsn— overrides the Sentry DSN; falls back to theSENTRY_DSNenv var, then theSentryInitializer.DSNconstant (the production project DSN, SaaS US region — DSNs are public by design, so it is committed). Sentry is SaaS by default; for a self-hosted instance setSENTRY_DSNin CI/dev instead. Privacy:sendDefaultPii=false,serverNameand user scrubbed inbeforeSend, onlyio.github.andrepgframes in-app; environment isdevelopmentunderflatpak.devtools.development, elseproduction; release tag isflatpak-devtools@<pluginVersion>. Reconfiguration happens inDiagnosticsInitializer(registered ascom.intellij.ide.AppLifecycleListenerinplugin.xml) and is re-triggered fromFlatpakSettingsConfigurable.apply().