| // Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'dart:io' show Platform; | |
| import 'package:path/path.dart' as path; | |
| import '../cli_util.dart'; | |
| /// The standard system paths for a Dart tool. | |
| /// | |
| /// These paths respect the following directory standards: | |
| /// | |
| /// - On Linux, the [XDG Base Directory | |
| /// Specification](https://specifications.freedesktop.org/basedir-spec/latest/). | |
| /// - On MacOS, the | |
| /// [Library](https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html) | |
| /// directory. | |
| /// - On Windows, `%APPDATA%` and `%LOCALAPPDATA%`. | |
| /// | |
| /// Note that [cacheHome], [configHome], [dataHome], [runtimeHome], and | |
| /// [stateHome] may be overlapping or nested. | |
| /// | |
| /// Note that the directories won't be created, the methods merely return the | |
| /// recommended locations. | |
| final class BaseDirectories { | |
| /// The name of the Dart tool. | |
| /// | |
| /// The name is used to provide a subdirectory inside the base directories. | |
| /// | |
| /// This should be a valid directory name on every operating system. The name | |
| /// is typically camel-cased. For example: `"MyApp"`. | |
| final String tool; | |
| /// The environment variables to use to determine the base directories. | |
| /// | |
| /// Defaults to [Platform.environment]. | |
| final Map<String, String> _environment; | |
| /// Constructs a [BaseDirectories] instance for the given [tool] name. | |
| /// | |
| /// The [environment] map, if provided, is used to determine the base | |
| /// directories. If omitted, it defaults to using [Platform.environment]. | |
| BaseDirectories(this.tool, {Map<String, String>? environment}) | |
| : _environment = environment ?? Platform.environment; | |
| /// Path of the directory where the tool will place its caches. | |
| /// | |
| /// The cache may be purged by the operating system or user at any time. | |
| /// Applications should be able to reconstruct any data stored here. If [tool] | |
| /// cannot handle data being purged, use [runtimeHome] or [dataHome] instead. | |
| /// | |
| /// This is a location appropriate for storing non-essential files that may be | |
| /// removed at any point. For example: intermediate compilation artifacts. | |
| /// | |
| /// The directory location depends on the current [Platform.operatingSystem]: | |
| /// - on **Windows**: | |
| /// - `%LOCALAPPDATA%\<tool>` | |
| /// - on **Mac OS**: | |
| /// - `$HOME/Library/Caches/<tool>` | |
| /// - on **Linux**: | |
| /// - `$XDG_CACHE_HOME/<tool>` if `$XDG_CACHE_HOME` is defined, and | |
| /// - `$HOME/.cache/<tool>` otherwise. | |
| /// | |
| /// The directory won't be created, the method merely returns the recommended | |
| /// location. | |
| /// | |
| /// On some platforms, this path may overlap with [runtimeHome] and | |
| /// [stateHome]. | |
| /// | |
| /// Throws an [EnvironmentNotFoundException] if a necessary environment | |
| /// variable is undefined. | |
| late final String cacheHome = path.join( | |
| _baseDirectory(_XdgBaseDirectoryKind.cache)!, | |
| tool, | |
| ); | |
| /// Path of the directory where the tool will place its configuration. | |
| /// | |
| /// The configuration may be synchronized across devices by the OS and may | |
| /// survive application removal. | |
| /// | |
| /// This is a location appropriate for storing application specific | |
| /// configuration for the current user. | |
| /// | |
| /// The directory location depends on the current [Platform.operatingSystem] | |
| /// and what file types are stored: | |
| /// - on **Windows**: | |
| /// - `%APPDATA%\<tool>` | |
| /// - on **Mac OS**: | |
| /// - `$HOME/Library/Application Support/<tool>` | |
| /// - on **Linux**: | |
| /// - `$XDG_CONFIG_HOME/<tool>` if `$XDG_CONFIG_HOME` is defined, and | |
| /// - `$HOME/.config/<tool>` otherwise. | |
| /// | |
| /// The directory won't be created, the method merely returns the recommended | |
| /// location. | |
| /// | |
| /// On some platforms, this path may overlap with [dataHome]. | |
| /// | |
| /// Throws an [EnvironmentNotFoundException] if a necessary environment | |
| /// variable is undefined. | |
| late final String configHome = path.join( | |
| _baseDirectory(_XdgBaseDirectoryKind.config)!, | |
| tool, | |
| ); | |
| /// Path of the directory where the tool will place its user data. | |
| /// | |
| /// The data may be backed up and synchronized to other devices by the | |
| /// operating system. For large data use [stateHome] instead. | |
| /// | |
| /// This is a location appropriate for storing application specific | |
| /// data for the current user. For example: documents created by the user. | |
| /// | |
| /// The directory location depends on the current [Platform.operatingSystem]: | |
| /// - on **Windows**: | |
| /// - `%APPDATA%\<tool>` | |
| /// - on **Mac OS**: | |
| /// - `$HOME/Library/Application Support/<tool>` | |
| /// - on **Linux**: | |
| /// - `$XDG_DATA_HOME/<tool>` if `$XDG_DATA_HOME` is defined, and | |
| /// - `$HOME/.local/share/<tool>` otherwise. | |
| /// | |
| /// The directory won't be created, the method merely returns the recommended | |
| /// location. | |
| /// | |
| /// On some platforms, this path may overlap with [configHome] and | |
| /// [stateHome]. | |
| /// | |
| /// Throws an [EnvironmentNotFoundException] if a necessary environment | |
| /// variable is undefined. | |
| late final String dataHome = path.join( | |
| _baseDirectory(_XdgBaseDirectoryKind.data)!, | |
| tool, | |
| ); | |
| /// Path of the directory where the tool will place its runtime data. | |
| /// | |
| /// The runtime data may be deleted in between user logins by the OS. For data | |
| /// that needs to persist between sessions, use [stateHome] instead. | |
| /// | |
| /// This is a location appropriate for storing runtime data for the current | |
| /// session. For example: undo history. | |
| /// | |
| /// This directory might be undefined on Linux, in such case a warning should | |
| /// be printed and a suitable fallback (such as a temporary directory) should | |
| /// be used. | |
| /// | |
| /// The directory location depends on the current [Platform.operatingSystem]: | |
| /// - on **Windows**: | |
| /// - `%LOCALAPPDATA%\<tool>` | |
| /// - on **Mac OS**: | |
| /// - `$HOME/Library/Caches/TemporaryItems/<tool>` | |
| /// - on **Linux**: | |
| /// - `$XDG_RUNTIME_DIR/<tool>` if `$XDG_RUNTIME_DIR` is defined, and | |
| /// - `null` otherwise. | |
| /// | |
| /// The directory won't be created, the method merely returns the recommended | |
| /// location. | |
| /// | |
| /// On some platforms, this path may overlap [cacheHome] and [stateHome] or be | |
| /// nested in [cacheHome]. | |
| /// | |
| /// Throws an [EnvironmentNotFoundException] if a necessary environment | |
| /// variable is undefined. | |
| late final String? runtimeHome = _join( | |
| _baseDirectory(_XdgBaseDirectoryKind.runtime), | |
| tool, | |
| ); | |
| /// Path of the directory where the tool will place its state. | |
| /// | |
| /// The state directory is likely not backed up or synchronized across | |
| /// devices by the OS. For data that may be backed up and synchronized, use | |
| /// [dataHome] instead. | |
| /// | |
| /// This is a location appropriate for storing data which is either not | |
| /// important enough, not small enough, or not portable enough to store in | |
| /// [dataHome]. For example: logs and indices. | |
| /// | |
| /// The directory location depends on the current [Platform.operatingSystem]: | |
| /// - on **Windows**: | |
| /// - `%LOCALAPPDATA%\<tool>` | |
| /// - on **Mac OS**: | |
| /// - `$HOME/Library/Application Support/<tool>` | |
| /// - on **Linux**: | |
| /// - `$XDG_STATE_HOME/<tool>` if `$XDG_STATE_HOME` is defined, and | |
| /// - `$HOME/.local/state/<tool>` otherwise. | |
| /// | |
| /// The directory won't be created, the method merely returns the recommended | |
| /// location. | |
| /// | |
| /// On some platforms, this path may overlap with [cacheHome], and | |
| /// [runtimeHome]. | |
| /// | |
| /// Throws an [EnvironmentNotFoundException] if a necessary environment | |
| /// variable is undefined. | |
| late final String stateHome = path.join( | |
| _baseDirectory(_XdgBaseDirectoryKind.state)!, | |
| tool, | |
| ); | |
| String? _baseDirectory(_XdgBaseDirectoryKind directoryKind) { | |
| if (Platform.isWindows) { | |
| return _baseDirectoryWindows(directoryKind); | |
| } | |
| if (Platform.isMacOS) { | |
| return _baseDirectoryMacOs(directoryKind); | |
| } | |
| if (Platform.isLinux) { | |
| return _baseDirectoryLinux(directoryKind); | |
| } | |
| throw UnsupportedError('Unsupported platform: ${Platform.operatingSystem}'); | |
| } | |
| String _baseDirectoryWindows(_XdgBaseDirectoryKind dir) => switch (dir) { | |
| _XdgBaseDirectoryKind.config || | |
| _XdgBaseDirectoryKind.data => _requireEnv('APPDATA'), | |
| _XdgBaseDirectoryKind.cache || | |
| _XdgBaseDirectoryKind.runtime || | |
| _XdgBaseDirectoryKind.state => _requireEnv('LOCALAPPDATA'), | |
| }; | |
| String _baseDirectoryMacOs(_XdgBaseDirectoryKind dir) => switch (dir) { | |
| _XdgBaseDirectoryKind.config || | |
| // `$HOME/Library/Preferences/` may only contain `.plist` files, so use | |
| // `Application Support` instead. | |
| _XdgBaseDirectoryKind.data || | |
| _XdgBaseDirectoryKind | |
| .state => path.join(_home, 'Library', 'Application Support'), | |
| _XdgBaseDirectoryKind.cache => path.join(_home, 'Library', 'Caches'), | |
| _XdgBaseDirectoryKind.runtime => | |
| // https://stackoverflow.com/a/76799489 | |
| path.join(_home, 'Library', 'Caches', 'TemporaryItems'), | |
| }; | |
| String? _baseDirectoryLinux(_XdgBaseDirectoryKind dir) { | |
| if (Platform.isLinux) { | |
| final xdgEnv = switch (dir) { | |
| _XdgBaseDirectoryKind.config => 'XDG_CONFIG_HOME', | |
| _XdgBaseDirectoryKind.data => 'XDG_DATA_HOME', | |
| _XdgBaseDirectoryKind.state => 'XDG_STATE_HOME', | |
| _XdgBaseDirectoryKind.cache => 'XDG_CACHE_HOME', | |
| _XdgBaseDirectoryKind.runtime => 'XDG_RUNTIME_DIR', | |
| }; | |
| final envVar = _environment[xdgEnv]; | |
| if (envVar != null) { | |
| return envVar; | |
| } | |
| } | |
| switch (dir) { | |
| case _XdgBaseDirectoryKind.runtime: | |
| // Applications should choose a different directory and print a warning. | |
| return null; | |
| case _XdgBaseDirectoryKind.cache: | |
| return path.join(_home, '.cache'); | |
| case _XdgBaseDirectoryKind.config: | |
| return path.join(_home, '.config'); | |
| case _XdgBaseDirectoryKind.data: | |
| return path.join(_home, '.local', 'share'); | |
| case _XdgBaseDirectoryKind.state: | |
| return path.join(_home, '.local', 'state'); | |
| } | |
| } | |
| String get _home => _requireEnv('HOME'); | |
| String _requireEnv(String name) => | |
| _environment[name] ?? (throw EnvironmentNotFoundException(name)); | |
| } | |
| /// A kind from the XDG base directory specification for Linux. | |
| /// | |
| /// MacOS and Windows have less kinds. | |
| enum _XdgBaseDirectoryKind { | |
| cache, | |
| config, | |
| data, | |
| // Executables are also mentioned in the XDG spec, but these do not have as | |
| // well defined of locations on Windows and MacOS. | |
| runtime, | |
| state, | |
| } | |
| String? _join(String? part1, String? part2) { | |
| if (part1 == null || part2 == null) { | |
| return null; | |
| } | |
| return path.join(part1, part2); | |
| } |