├── .clang-format ├── .github └── workflows │ └── main.yml ├── .gitignore ├── README.md ├── emacs ├── emacs-x-resource-name.patch ├── emacsclient ├── native-comp.patch ├── setup-env ├── Makefile └── setup-env.c ├── site-lisp └── site-start.el ├── snapcraft.yaml ├── spread.yaml ├── tests ├── native-comp │ └── task.yaml ├── setup-env │ └── task.yaml └── treesit │ └── task.yaml └── treesit.patch /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignArrayOfStructures: None 7 | AlignConsecutiveAssignments: 8 | Enabled: false 9 | AcrossEmptyLines: false 10 | AcrossComments: false 11 | AlignCompound: false 12 | AlignFunctionPointers: false 13 | PadOperators: true 14 | AlignConsecutiveBitFields: 15 | Enabled: false 16 | AcrossEmptyLines: false 17 | AcrossComments: false 18 | AlignCompound: false 19 | AlignFunctionPointers: false 20 | PadOperators: false 21 | AlignConsecutiveDeclarations: 22 | Enabled: false 23 | AcrossEmptyLines: false 24 | AcrossComments: false 25 | AlignCompound: false 26 | AlignFunctionPointers: false 27 | PadOperators: false 28 | AlignConsecutiveMacros: 29 | Enabled: false 30 | AcrossEmptyLines: false 31 | AcrossComments: false 32 | AlignCompound: false 33 | AlignFunctionPointers: false 34 | PadOperators: false 35 | AlignConsecutiveShortCaseStatements: 36 | Enabled: false 37 | AcrossEmptyLines: false 38 | AcrossComments: false 39 | AlignCaseColons: false 40 | AlignEscapedNewlines: Right 41 | AlignOperands: Align 42 | AlignTrailingComments: 43 | Kind: Always 44 | OverEmptyLines: 0 45 | AllowAllArgumentsOnNextLine: true 46 | AllowAllParametersOfDeclarationOnNextLine: true 47 | AllowBreakBeforeNoexceptSpecifier: Never 48 | AllowShortBlocksOnASingleLine: Never 49 | AllowShortCaseLabelsOnASingleLine: false 50 | AllowShortCompoundRequirementOnASingleLine: true 51 | AllowShortEnumsOnASingleLine: true 52 | AllowShortFunctionsOnASingleLine: All 53 | AllowShortIfStatementsOnASingleLine: Never 54 | AllowShortLambdasOnASingleLine: All 55 | AllowShortLoopsOnASingleLine: false 56 | AlwaysBreakAfterDefinitionReturnType: None 57 | AlwaysBreakAfterReturnType: None 58 | AlwaysBreakBeforeMultilineStrings: false 59 | AlwaysBreakTemplateDeclarations: MultiLine 60 | AttributeMacros: 61 | - __capability 62 | BinPackArguments: true 63 | BinPackParameters: true 64 | BitFieldColonSpacing: Both 65 | BraceWrapping: 66 | AfterCaseLabel: false 67 | AfterClass: false 68 | AfterControlStatement: Never 69 | AfterEnum: false 70 | AfterExternBlock: false 71 | AfterFunction: false 72 | AfterNamespace: false 73 | AfterObjCDeclaration: false 74 | AfterStruct: false 75 | AfterUnion: false 76 | BeforeCatch: false 77 | BeforeElse: false 78 | BeforeLambdaBody: false 79 | BeforeWhile: false 80 | IndentBraces: false 81 | SplitEmptyFunction: true 82 | SplitEmptyRecord: true 83 | SplitEmptyNamespace: true 84 | BreakAdjacentStringLiterals: true 85 | BreakAfterAttributes: Leave 86 | BreakAfterJavaFieldAnnotations: false 87 | BreakArrays: true 88 | BreakBeforeBinaryOperators: None 89 | BreakBeforeConceptDeclarations: Always 90 | BreakBeforeBraces: Attach 91 | BreakBeforeInlineASMColon: OnlyMultiline 92 | BreakBeforeTernaryOperators: true 93 | BreakConstructorInitializers: BeforeColon 94 | BreakInheritanceList: BeforeColon 95 | BreakStringLiterals: true 96 | ColumnLimit: 80 97 | CommentPragmas: '^ IWYU pragma:' 98 | CompactNamespaces: false 99 | ConstructorInitializerIndentWidth: 2 100 | ContinuationIndentWidth: 2 101 | Cpp11BracedListStyle: true 102 | DerivePointerAlignment: false 103 | DisableFormat: false 104 | EmptyLineAfterAccessModifier: Never 105 | EmptyLineBeforeAccessModifier: LogicalBlock 106 | ExperimentalAutoDetectBinPacking: false 107 | FixNamespaceComments: true 108 | ForEachMacros: 109 | - foreach 110 | - Q_FOREACH 111 | - BOOST_FOREACH 112 | IfMacros: 113 | - KJ_IF_MAYBE 114 | IncludeBlocks: Preserve 115 | IncludeCategories: 116 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 117 | Priority: 2 118 | SortPriority: 0 119 | CaseSensitive: false 120 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 121 | Priority: 3 122 | SortPriority: 0 123 | CaseSensitive: false 124 | - Regex: '.*' 125 | Priority: 1 126 | SortPriority: 0 127 | CaseSensitive: false 128 | IncludeIsMainRegex: '(Test)?$' 129 | IncludeIsMainSourceRegex: '' 130 | IndentAccessModifiers: false 131 | IndentCaseBlocks: false 132 | IndentCaseLabels: false 133 | IndentExternBlock: AfterExternBlock 134 | IndentGotoLabels: true 135 | IndentPPDirectives: None 136 | IndentRequiresClause: true 137 | IndentWidth: 2 138 | IndentWrappedFunctionNames: false 139 | InsertBraces: false 140 | InsertNewlineAtEOF: false 141 | InsertTrailingCommas: None 142 | IntegerLiteralSeparator: 143 | Binary: 0 144 | BinaryMinDigits: 0 145 | Decimal: 0 146 | DecimalMinDigits: 0 147 | Hex: 0 148 | HexMinDigits: 0 149 | JavaScriptQuotes: Leave 150 | JavaScriptWrapImports: true 151 | KeepEmptyLinesAtTheStartOfBlocks: true 152 | KeepEmptyLinesAtEOF: false 153 | LambdaBodyIndentation: Signature 154 | LineEnding: DeriveLF 155 | MacroBlockBegin: '' 156 | MacroBlockEnd: '' 157 | MaxEmptyLinesToKeep: 1 158 | NamespaceIndentation: None 159 | ObjCBinPackProtocolList: Auto 160 | ObjCBlockIndentWidth: 2 161 | ObjCBreakBeforeNestedBlockParam: true 162 | ObjCSpaceAfterProperty: false 163 | ObjCSpaceBeforeProtocolList: true 164 | PackConstructorInitializers: BinPack 165 | PenaltyBreakAssignment: 2 166 | PenaltyBreakBeforeFirstCallParameter: 19 167 | PenaltyBreakComment: 300 168 | PenaltyBreakFirstLessLess: 120 169 | PenaltyBreakOpenParenthesis: 0 170 | PenaltyBreakScopeResolution: 500 171 | PenaltyBreakString: 1000 172 | PenaltyBreakTemplateDeclaration: 10 173 | PenaltyExcessCharacter: 1000000 174 | PenaltyIndentedWhitespace: 0 175 | PenaltyReturnTypeOnItsOwnLine: 60 176 | PointerAlignment: Right 177 | PPIndentWidth: -1 178 | QualifierAlignment: Leave 179 | ReferenceAlignment: Pointer 180 | ReflowComments: true 181 | RemoveBracesLLVM: false 182 | RemoveParentheses: Leave 183 | RemoveSemicolon: false 184 | RequiresClausePosition: OwnLine 185 | RequiresExpressionIndentation: OuterScope 186 | SeparateDefinitionBlocks: Leave 187 | ShortNamespaceLines: 1 188 | SkipMacroDefinitionBody: false 189 | SortIncludes: CaseSensitive 190 | SortJavaStaticImport: Before 191 | SortUsingDeclarations: LexicographicNumeric 192 | SpaceAfterCStyleCast: false 193 | SpaceAfterLogicalNot: false 194 | SpaceAfterTemplateKeyword: true 195 | SpaceAroundPointerQualifiers: Default 196 | SpaceBeforeAssignmentOperators: true 197 | SpaceBeforeCaseColon: false 198 | SpaceBeforeCpp11BracedList: false 199 | SpaceBeforeCtorInitializerColon: true 200 | SpaceBeforeInheritanceColon: true 201 | SpaceBeforeJsonColon: false 202 | SpaceBeforeParens: ControlStatements 203 | SpaceBeforeParensOptions: 204 | AfterControlStatements: true 205 | AfterForeachMacros: true 206 | AfterFunctionDefinitionName: false 207 | AfterFunctionDeclarationName: false 208 | AfterIfMacros: true 209 | AfterOverloadedOperator: false 210 | AfterPlacementOperator: true 211 | AfterRequiresInClause: false 212 | AfterRequiresInExpression: false 213 | BeforeNonEmptyParentheses: false 214 | SpaceBeforeRangeBasedForLoopColon: true 215 | SpaceBeforeSquareBrackets: false 216 | SpaceInEmptyBlock: false 217 | SpacesBeforeTrailingComments: 1 218 | SpacesInAngles: Never 219 | SpacesInContainerLiterals: true 220 | SpacesInLineCommentPrefix: 221 | Minimum: 1 222 | Maximum: -1 223 | SpacesInParens: Never 224 | SpacesInParensOptions: 225 | InCStyleCasts: false 226 | InConditionalStatements: false 227 | InEmptyParentheses: false 228 | Other: false 229 | SpacesInSquareBrackets: false 230 | Standard: Latest 231 | StatementAttributeLikeMacros: 232 | - Q_EMIT 233 | StatementMacros: 234 | - Q_UNUSED 235 | - QT_REQUIRE_VERSION 236 | TabWidth: 8 237 | UseTab: Never 238 | VerilogBreakBetweenInstancePorts: true 239 | WhitespaceSensitiveMacros: 240 | - BOOST_PP_STRINGIZE 241 | - CF_SWIFT_NAME 242 | - NS_SWIFT_NAME 243 | - PP_STRINGIZE 244 | - STRINGIZE 245 | ... 246 | 247 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: build-snap 2 | 3 | on: 4 | pull_request: 5 | push: 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: snapcore/action-build@v1 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /*.snap 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![GNU Emacs logo](https://www.gnu.org/software/emacs/images/emacs.png "GNU Emacs") 2 | 3 | # GNU Emacs # 4 | 5 | ------------------------------------------------------------------------------- 6 | 7 | **GNU Emacs in a snap** - the extensible, customizable, self-documenting real-time display editor 8 | 9 | [![GNU Emacs](https://snapcraft.io/emacs/badge.svg)](https://snapcraft.io/emacs) 10 | [![GNU Emacs](https://snapcraft.io//emacs/trending.svg?name=0)](https://snapcraft.io/emacs) 11 | 12 | ## Installation ## 13 | 14 | [![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/emacs) 15 | 16 | ``` shell 17 | sudo snap install emacs --classic 18 | ``` 19 | 20 | ([Don't have snapd installed?](https://snapcraft.io/docs/core/install)) 21 | 22 | ## Build it yourself ## 23 | 24 | ```shell 25 | # clone this repo 26 | git clone https://github.com/alexmurray/emacs-snap 27 | cd emacs-snap 28 | 29 | # install snapcraft and multipass tooling needed to build the snap in a reproducible way 30 | sudo snap install snapcraft --classic 31 | sudo snap install multipass --beta --classic 32 | 33 | # build the snap 34 | snapcraft 35 | 36 | # install the snap (--dangerous signals this is not signed and hence not trusted) 37 | sudo snap install ./emacs*.snap --dangerous 38 | ``` 39 | -------------------------------------------------------------------------------- /emacs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Launch emacs with the most appropriate toolkit (currently gtk (for real x11) 4 | # or wayland) 5 | 6 | # The environment variable EMACS_TOOLKIT can be used to specify the preferred toolkit. 7 | # The value should be one of: gtk or wayland 8 | 9 | # If the environment variable is not set, the script will try to detect the 10 | # toolkit to use by checking if the XDG_SESSION_TYPE environment variable is 11 | # set to wayland or x11. 12 | 13 | if [ -n "${XDG_SESSION_TYPE}" ]; then 14 | if [ "${XDG_SESSION_TYPE}" = "wayland" ]; then 15 | : "${EMACS_TOOLKIT:=wayland}" 16 | else 17 | : "${EMACS_TOOLKIT:=gtk}" 18 | fi 19 | else 20 | : "${EMACS_TOOLKIT:=gtk}" 21 | fi 22 | 23 | case "${EMACS_TOOLKIT}" in 24 | gtk|wayland) 25 | ;; 26 | *) 27 | echo "Invalid value for EMACS_TOOLKIT: ${EMACS_TOOLKIT} - must be either gtk or wayland" 28 | exit 1 29 | ;; 30 | esac 31 | 32 | exec "$SNAP/usr/bin/emacs-${EMACS_TOOLKIT}" "$@" 33 | 34 | -------------------------------------------------------------------------------- /emacs-x-resource-name.patch: -------------------------------------------------------------------------------- 1 | diff --git a/lisp/term/pgtk-win.el b/lisp/term/pgtk-win.el 2 | index 2e03e7f57a5..a816224b703 100644 3 | --- a/lisp/term/pgtk-win.el 4 | +++ b/lisp/term/pgtk-win.el 5 | @@ -116,7 +116,7 @@ DISPLAY is the name of the display Emacs should connect to." 6 | (when (boundp 'x-resource-name) 7 | (unless (stringp x-resource-name) 8 | (let (i) 9 | - (setq x-resource-name (copy-sequence invocation-name)) 10 | + (setq x-resource-name (copy-sequence "emacs")) 11 | 12 | ;; Change any . or * characters in x-resource-name to hyphens, 13 | ;; so as not to choke when we use it in X resource queries. 14 | diff --git a/lisp/term/x-win.el b/lisp/term/x-win.el 15 | index 98dd576fea2..ac1fae6d600 100644 16 | --- a/lisp/term/x-win.el 17 | +++ b/lisp/term/x-win.el 18 | @@ -1231,7 +1231,7 @@ This returns an error if any Emacs frames are X frames." 19 | ;; Make sure we have a valid resource name. 20 | (or (stringp x-resource-name) 21 | (let (i) 22 | - (setq x-resource-name (copy-sequence invocation-name)) 23 | + (setq x-resource-name (copy-sequence "emacs")) 24 | 25 | ;; Change any . or * characters in x-resource-name to hyphens, 26 | ;; so as not to choke when we use it in X resource queries. 27 | diff --git a/src/pgtkfns.c b/src/pgtkfns.c 28 | index 5f806e18090..abfc239a2c5 100644 29 | --- a/src/pgtkfns.c 30 | +++ b/src/pgtkfns.c 31 | @@ -155,7 +155,7 @@ pgtk_display_info_for_name (Lisp_Object name) 32 | } 33 | 34 | /* Use this general default value to start with. */ 35 | - Vx_resource_name = Vinvocation_name; 36 | + Vx_resource_name = build_string ("emacs"); 37 | 38 | validate_x_resource_name (); 39 | 40 | @@ -1218,7 +1218,7 @@ This function is an internal primitive--use `make-frame' instead. */ ) 41 | 42 | /* Use this general default value to start with 43 | until we know if this frame has a specified name. */ 44 | - Vx_resource_name = Vinvocation_name; 45 | + Vx_resource_name = build_string("emacs"); 46 | 47 | display = 48 | gui_display_get_arg (dpyinfo, parms, Qterminal, 0, 0, RES_TYPE_NUMBER); 49 | diff --git a/src/xfns.c b/src/xfns.c 50 | index 5a618908be1..19343ea8f82 100644 51 | --- a/src/xfns.c 52 | +++ b/src/xfns.c 53 | @@ -4629,7 +4629,7 @@ This function is an internal primitive--use `make-frame' instead. */) 54 | 55 | /* Use this general default value to start with 56 | until we know if this frame has a specified name. */ 57 | - Vx_resource_name = Vinvocation_name; 58 | + Vx_resource_name = build_string ("emacs"); 59 | 60 | display = gui_display_get_arg (dpyinfo, parms, Qterminal, 0, 0, 61 | RES_TYPE_NUMBER); 62 | @@ -7281,7 +7281,7 @@ x_display_info_for_name (Lisp_Object name) 63 | return dpyinfo; 64 | 65 | /* Use this general default value to start with. */ 66 | - Vx_resource_name = Vinvocation_name; 67 | + Vx_resource_name = build_string ("emacs"); 68 | 69 | validate_x_resource_name (); 70 | 71 | diff --git a/src/xterm.c b/src/xterm.c 72 | index 524e2a32574..b30ff50f97c 100644 73 | --- a/src/xterm.c 74 | +++ b/src/xterm.c 75 | @@ -29489,7 +29489,7 @@ x_term_init (Lisp_Object display_name, char *xrm_option, char *resource_name) 76 | argv[argc] = 0; 77 | 78 | argc = 0; 79 | - argv[argc++] = initial_argv[0]; 80 | + argv[argc++] = "emacs"; 81 | 82 | if (! NILP (display_name)) 83 | { 84 | @@ -29514,6 +29514,7 @@ x_term_init (Lisp_Object display_name, char *xrm_option, char *resource_name) 85 | /* gtk_init does set_locale. Fix locale before and after. */ 86 | fixup_locale (); 87 | unrequest_sigio (); /* See comment in x_display_ok. */ 88 | + g_set_prgname ("emacs"); 89 | gtk_init (&argc, &argv2); 90 | request_sigio (); 91 | 92 | -------------------------------------------------------------------------------- /emacsclient: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Launch emacsclient with the most appropriate toolkit (currently gtk (for real 3 | # x11) or wayland) 4 | 5 | # The environment variable EMACS_TOOLKIT can be used to specify the preferred toolkit. 6 | # The value should be one of: gtk or wayland 7 | 8 | # If the environment variable is not set, the script will try to detect the 9 | # toolkit to use by checking if the XDG_SESSION_TYPE environment variable is 10 | # set to wayland or x11. 11 | 12 | if [ -n "${XDG_SESSION_TYPE}" ]; then 13 | if [ "${XDG_SESSION_TYPE}" = "wayland" ]; then 14 | : "${EMACS_TOOLKIT:=wayland}" 15 | else 16 | : "${EMACS_TOOLKIT:=gtk}" 17 | fi 18 | else 19 | : "${EMACS_TOOLKIT:=gtk}" 20 | fi 21 | 22 | case "${EMACS_TOOLKIT}" in 23 | gtk|wayland) 24 | ;; 25 | *) 26 | echo "Invalid value for EMACS_TOOLKIT: ${EMACS_TOOLKIT} - must be either gtk or wayland" 27 | exit 1 28 | ;; 29 | esac 30 | 31 | EMACSCLIENT="emacsclient-${EMACS_TOOLKIT}" 32 | 33 | # add logic from the upstream emacsclient.desktop script which supports 34 | # spawning a new frame when launched with no arguments but only do this 35 | # when we are launched from the desktop file which we can detect via the 36 | # GIO_LAUNCHED_DESKTOP_FILE environment variable and associated 37 | # GIO_LAUNCHED_DESKTOP_FILE_PID 38 | if [ -n "$GIO_LAUNCHED_DESKTOP_FILE" ] && [ "$$" -eq "$GIO_LAUNCHED_DESKTOP_FILE_PID" ]; then 39 | # the following is the logic from the upstream emacsclient.desktop file other 40 | # than the wayland display handling if statement 41 | if [ -n "$*" ]; then 42 | # only set --display if EMACS_TOOLKIT is not wayland - 43 | # https://github.com/alexmurray/emacs-snap/issues/98 44 | if [ "${EMACS_TOOLKIT}" != "wayland" ]; then 45 | set -- --display="$DISPLAY" "$@" 46 | fi 47 | exec "$SNAP/usr/bin/$EMACSCLIENT" --alternate-editor= "$@" 48 | else 49 | exec "$SNAP/usr/bin/$EMACSCLIENT" --alternate-editor= --create-frame 50 | fi 51 | else 52 | exec "$SNAP/usr/bin/$EMACSCLIENT" "$@" 53 | fi 54 | -------------------------------------------------------------------------------- /native-comp.patch: -------------------------------------------------------------------------------- 1 | diff --git a/usr/share/emacs/@@VERSION@@/lisp/emacs-lisp/comp.el b/usr/share/emacs/@@VERSION@@/lisp/emacs-lisp/comp.el 2 | index e97832455b9..b04cfa00c33 100644 3 | --- a/usr/share/emacs/@@VERSION@@/lisp/emacs-lisp/comp.el 4 | +++ b/usr/share/emacs/@@VERSION@@/lisp/emacs-lisp/comp.el 5 | @@ -188,7 +188,8 @@ and above." 6 | 7 | (defcustom native-comp-driver-options 8 | (cond ((eq system-type 'darwin) '("-Wl,-w")) 9 | - ((eq system-type 'cygwin) '("-Wl,-dynamicbase"))) 10 | + ((eq system-type 'cygwin) '("-Wl,-dynamicbase")) 11 | + ((getenv "EMACS_SNAP_USER_COMMON") (list (concat "--sysroot=" (file-name-as-directory (getenv "EMACS_SNAP_USER_COMMON")) "sysroot/") (concat "-B" (file-name-as-directory (getenv "EMACS_SNAP_USER_COMMON")) "sysroot/usr/lib/gcc/")))) 12 | "Options passed verbatim to the native compiler's back-end driver. 13 | Note that not all options are meaningful; typically only the options 14 | affecting the assembler and linker are likely to be useful. 15 | -------------------------------------------------------------------------------- /setup-env/Makefile: -------------------------------------------------------------------------------- 1 | setup-env: setup-env.c 2 | gcc setup-env.c -o setup-env 3 | 4 | install: setup-env 5 | install -m 755 setup-env $(DESTDIR)/ 6 | -------------------------------------------------------------------------------- /setup-env/setup-env.c: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a helper program to setup the environment for the emacs snap to run 3 | * correctly. 4 | * Copyright 2025 Alex Murray 5 | * License: GPL-3.0+ 6 | */ 7 | 8 | #define _GNU_SOURCE // for asprintf 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | // check if str ends with suffix 24 | int str_ends_with(const char *str, const char *suffix) { 25 | size_t str_len = strlen(str); 26 | size_t suffix_len = strlen(suffix); 27 | if (suffix_len > str_len) { 28 | return 0; 29 | } 30 | return strncmp(str + str_len - suffix_len, suffix, suffix_len) == 0; 31 | } 32 | 33 | // NOTE: in general we don't bother to free any of the dynamically allocated 34 | // memory since this is not a long-lived process so we don't care about memory 35 | // leaks as it will all get cleaned up when the process exits 36 | int main(int argc, char *argv[]) { 37 | int res; 38 | struct stat st; 39 | const char *snap; 40 | const char *snap_arch; 41 | const char *snap_user_common; 42 | char *arch = NULL; 43 | char *gdk_cache_dir = NULL; 44 | char *gio_module_dir = NULL; 45 | char *gdk_pixbuf_module_file = NULL; 46 | char *gdk_pixbuf_moduledir = NULL; 47 | char *gdk_pixbuf_query_loaders = NULL; 48 | char *fontconfig_cache_dir = NULL; 49 | char *fontconfig_file = NULL; 50 | char *gtk_im_module_dir = NULL; 51 | char *gtk_im_module_file = NULL; 52 | char *gtk_query_immodules = NULL; 53 | const char *path = NULL; 54 | 55 | snap = getenv("SNAP"); 56 | if (snap == NULL) { 57 | fprintf(stderr, "SNAP is not set\n"); 58 | exit(1); 59 | } 60 | 61 | snap_arch = getenv("SNAP_ARCH"); 62 | if (snap_arch == NULL) { 63 | fprintf(stderr, "SNAP_ARCH is not set\n"); 64 | exit(1); 65 | } 66 | 67 | if (strcmp(snap_arch, "amd64") == 0) { 68 | asprintf(&arch, "x86_64-linux-gnu"); 69 | } else if (strcmp(snap_arch, "armhf") == 0) { 70 | asprintf(&arch, "arm-linux-gnueabihf"); 71 | } else if (strcmp(snap_arch, "arm64") == 0) { 72 | asprintf(&arch, "aarch64-linux-gnu"); 73 | } else { 74 | asprintf(&arch, "%s-linux-gnu", snap_arch); 75 | } 76 | 77 | snap_user_common = getenv("SNAP_USER_COMMON"); 78 | if (snap_user_common == NULL) { 79 | fprintf(stderr, "SNAP_USER_COMMON is not set\n"); 80 | exit(1); 81 | } 82 | 83 | asprintf(&gdk_cache_dir, "%s/.cache", snap_user_common); 84 | mkdir(gdk_cache_dir, 0700); 85 | 86 | asprintf(&gio_module_dir, "%s/usr/lib/%s/gio/modules", snap, arch); 87 | setenv("GIO_MODULE_DIR", gio_module_dir, 1); 88 | 89 | asprintf(&gdk_pixbuf_module_file, "%s/gdk-pixbuf-loaders.cache", 90 | gdk_cache_dir); 91 | setenv("GDK_PIXBUF_MODULE_FILE", gdk_pixbuf_module_file, 1); 92 | 93 | asprintf(&gdk_pixbuf_moduledir, "%s/usr/lib/%s/gdk-pixbuf-2.0/2.10.0/loaders", 94 | snap, arch); 95 | setenv("GDK_PIXBUF_MODULEDIR", gdk_pixbuf_moduledir, 1); 96 | 97 | asprintf(&gdk_pixbuf_query_loaders, 98 | "%s/usr/lib/%s/gdk-pixbuf-2.0/gdk-pixbuf-query-loaders", snap, arch); 99 | res = stat(gdk_pixbuf_query_loaders, &st); 100 | 101 | if (res == 0) { 102 | // execute gdk_pixbuf_query_loaders and redirect output to 103 | // gdk_pixbuf_module_file 104 | pid_t child = fork(); 105 | if (child == -1) { 106 | fprintf(stderr, "Failed to fork: %s\n", strerror(errno)); 107 | exit(1); 108 | } 109 | if (child == 0) { 110 | // we are the child - redirect our output to gdk_pixbuf_module_file and 111 | // exec gdk_pixbuf_query_loaders 112 | int fd = open(gdk_pixbuf_module_file, O_CREAT | O_TRUNC | O_WRONLY, 0644); 113 | if (fd < 0) { 114 | fprintf(stderr, "Failed to open %s: %s\n", gdk_pixbuf_module_file, 115 | strerror(errno)); 116 | exit(1); 117 | } 118 | res = dup2(fd, 1); 119 | if (res < 0) { 120 | fprintf(stderr, "Failed to dup2: %s\n", strerror(errno)); 121 | exit(1); 122 | } 123 | res = execl(gdk_pixbuf_query_loaders, gdk_pixbuf_query_loaders, NULL); 124 | if (res < 0) { 125 | fprintf(stderr, "Failed to exec %s: %s\n", gdk_pixbuf_query_loaders, 126 | strerror(errno)); 127 | exit(1); 128 | } 129 | } 130 | // wait for child to execute 131 | waitpid(child, NULL, 0); 132 | } 133 | 134 | asprintf(&fontconfig_cache_dir, "%s/.cache/fontconfig", snap_user_common); 135 | mkdir(fontconfig_cache_dir, 0700); 136 | 137 | asprintf(&fontconfig_file, "%s/fonts.conf", snap_user_common); 138 | setenv("FONTCONFIG_FILE", fontconfig_file, 1); 139 | 140 | // always recreate the fontconfig_file to ensure we overwrite any old one that 141 | // may have come from a different base snap version in a previous release of 142 | // the emacs snap 143 | { 144 | char *source_path; 145 | int infd, outfd; 146 | FILE *infile; 147 | char *line = NULL; 148 | size_t n; 149 | ssize_t len; 150 | regex_t re; 151 | regmatch_t matches[1]; 152 | char *cachedir_entry; 153 | 154 | res = regcomp(&re, "", REG_EXTENDED); 155 | if (res != 0) { 156 | fprintf(stderr, "Failed to compile regex\n"); 157 | exit(1); 158 | } 159 | asprintf(&cachedir_entry, " %s\n", 160 | fontconfig_cache_dir); 161 | asprintf(&source_path, "%s/etc/fonts/fonts.conf", snap); 162 | infd = open(source_path, O_RDONLY); 163 | if (infd == -1) { 164 | fprintf(stderr, "Failed to open source fontconfig file %s: %s\n", 165 | source_path, strerror(errno)); 166 | } 167 | outfd = 168 | open(fontconfig_file, O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC, 0644); 169 | if (outfd == -1) { 170 | fprintf(stderr, "Failed to open dest fontconfig file %s: %s\n", 171 | fontconfig_file, strerror(errno)); 172 | } 173 | res = fstat(infd, &st); 174 | if (res == -1) { 175 | fprintf(stderr, "Failed to stat infd: %s\n", strerror(errno)); 176 | } 177 | infile = fdopen(infd, "r"); 178 | if (infile == NULL) { 179 | fprintf(stderr, "Failed to open %s for streaming: %s\n", source_path, 180 | strerror(errno)); 181 | } 182 | // read infd line by line and write the output to outfd - check along the 183 | // way for cachedir entries and replace these with our own 184 | // fontconfig_cache_dir 185 | while ((len = getline(&line, &n, infile)) != -1) { 186 | res = regexec(&re, line, 1, matches, 0); 187 | if (res == 0) { 188 | write(outfd, cachedir_entry, strlen(cachedir_entry)); 189 | } else { 190 | write(outfd, line, strlen(line)); 191 | } 192 | free(line); 193 | line = NULL; 194 | } 195 | fclose(infile); // fclose will close infd 196 | close(outfd); 197 | free(source_path); 198 | free(cachedir_entry); 199 | regfree(&re); 200 | } 201 | 202 | // immodule cache 203 | asprintf(>k_im_module_dir, "%s/immodules", gdk_cache_dir); 204 | mkdir(gtk_im_module_dir, 0700); 205 | 206 | asprintf(>k_im_module_file, "%s/immodules.cache", gtk_im_module_dir); 207 | asprintf(>k_query_immodules, 208 | "%s/usr/lib/%s/libgtk-3-0/gtk-query-immodules-3.0", snap, arch); 209 | res = stat(gtk_query_immodules, &st); 210 | if (res == 0) { 211 | // execute gtk_query_immodules over the list of immodules and redirect 212 | // output to gtk_im_module_file 213 | // first enumerate the list of immmodules 214 | char **args = NULL; 215 | int nargs = 0; 216 | char *gtk_immodules_path = NULL; 217 | DIR *dir = NULL; 218 | struct dirent *entry; 219 | 220 | // first argument should be program name as argv[0] 221 | args = realloc(args, (nargs + 1) * sizeof(char *)); 222 | if (args == NULL) { 223 | fprintf(stderr, "Failed to allocate argument list: %s\n", 224 | strerror(errno)); 225 | exit(1); 226 | } 227 | args[0] = strdup(gtk_query_immodules); 228 | nargs++; 229 | 230 | asprintf(>k_immodules_path, "%s/usr/lib/%s/gtk-3.0/3.0.0/immodules", snap, 231 | arch); 232 | dir = opendir(gtk_immodules_path); 233 | while (dir != NULL && (entry = readdir(dir)) != NULL) { 234 | // if starts with im- and ends with .so then append this to the list of 235 | // modules 236 | if ((strncmp(entry->d_name, "im-", strlen("im-")) == 0) && 237 | (strlen(entry->d_name) > 3) && 238 | (strncmp(entry->d_name + strlen(entry->d_name) - 3, ".so", 3) == 0)) { 239 | char **args_ = realloc(args, (nargs + 1) * sizeof(char *)); 240 | if (args_ == NULL) { 241 | fprintf(stderr, "Failed to realloc args list: %s\n", strerror(errno)); 242 | free(args); 243 | args = NULL; 244 | nargs = 0; 245 | break; 246 | } 247 | args = args_; 248 | asprintf(&args[nargs], "%s/%s", gtk_immodules_path, entry->d_name); 249 | nargs++; 250 | } 251 | 252 | // add a NULL terminator to the list of arguments 253 | char **args_ = realloc(args, (nargs + 1) * sizeof(char *)); 254 | if (args_ == NULL) { 255 | fprintf(stderr, "Failed to realloc args list: %s\n", strerror(errno)); 256 | free(args); 257 | args = NULL; 258 | nargs = 0; 259 | goto fork; 260 | } 261 | args = args_; 262 | args[nargs] = NULL; 263 | } 264 | 265 | fork: { 266 | pid_t child = fork(); 267 | if (child == -1) { 268 | fprintf(stderr, "Failed to fork: %s\n", strerror(errno)); 269 | exit(1); 270 | } 271 | if (child == 0) { 272 | // we are the child - redirect our output to gtk_im_module_file and 273 | // exec gtk_query_immodules with args as arguments 274 | int fd = open(gtk_im_module_file, O_CREAT | O_TRUNC | O_WRONLY, 0644); 275 | if (fd < 0) { 276 | fprintf(stderr, "Failed to open %s: %s\n", gtk_im_module_file, 277 | strerror(errno)); 278 | exit(1); 279 | } 280 | res = dup2(fd, 1); 281 | if (res < 0) { 282 | fprintf(stderr, "Failed to dup2: %s\n", strerror(errno)); 283 | exit(1); 284 | } 285 | res = execv(gtk_query_immodules, args); 286 | if (res < 0) { 287 | fprintf(stderr, "Failed to exec %s: %s\n", gtk_query_immodules, 288 | strerror(errno)); 289 | exit(1); 290 | } 291 | } 292 | // wait for child to execute 293 | waitpid(child, NULL, 0); 294 | } 295 | 296 | setenv("GTK_IM_MODULE_FILE", gtk_im_module_file, 1); 297 | } 298 | 299 | // set GTK_PATH to find gtk modules from the snap and not the host to avoid 300 | // startup errors like: 301 | // 302 | // Gtk-Message: 04:21:40.701: Failed to load module "pk-gtk-module" 303 | // Gtk-Message: 04:21:40.701: Failed to load module "canberra-gtk-module" 304 | { 305 | char *gtk_path; 306 | asprintf(>k_path, "%s/usr/lib/%s/gtk-3.0", snap, arch); 307 | setenv("GTK_PATH", gtk_path, 1); 308 | } 309 | 310 | // set PATH to include binaries from the snap since native comp needs to find 311 | // as and other similar binaries 312 | path = getenv("PATH"); 313 | if (path != NULL) { 314 | char *new_path; 315 | asprintf(&new_path, "%s:%s/usr/bin", path, snap); 316 | setenv("PATH", new_path, 1); 317 | } 318 | 319 | // setup sylinks so we can create a sysroot for native comp via symlinks to 320 | // avoid taking disk space 321 | { 322 | char *sysroot, *target, *linkpath; 323 | asprintf(&sysroot, "%s/sysroot", snap_user_common); 324 | res = mkdir(sysroot, S_IRUSR | S_IWUSR | S_IXUSR); 325 | if (res < 0 && errno != EEXIST) { 326 | fprintf(stderr, "Failed to create sysroot dir at %s: %s\n", sysroot, 327 | strerror(errno)); 328 | exit(1); 329 | } 330 | asprintf(&target, "%s/usr", snap); 331 | asprintf(&linkpath, "%s/usr", sysroot); 332 | unlink(linkpath); 333 | res = symlink(target, linkpath); 334 | if (res < 0 && errno != EEXIST) { 335 | fprintf(stderr, "Failed to symlink %s to %s: %s\n", target, linkpath, 336 | strerror(errno)); 337 | exit(1); 338 | } 339 | 340 | // also create lib64 if it exists 341 | struct stat sb; 342 | asprintf(&target, "%s/usr/lib64", sysroot); 343 | res = stat(target, &sb); 344 | if (res == 0) { 345 | asprintf(&linkpath, "%s/lib64", sysroot); 346 | unlink(linkpath); 347 | res = symlink(target, linkpath); 348 | if (res < 0 && errno != EEXIST) { 349 | fprintf(stderr, "Failed to symlink %s to %s: %s\n", target, linkpath, 350 | strerror(errno)); 351 | exit(1); 352 | } 353 | } 354 | } 355 | 356 | // set GSETTINGS_SCHEMA_DIR for 357 | // https://github.com/alexmurray/emacs-snap/issues/103 and 358 | // https://github.com/alexmurray/emacs-snap/issues/104 by taking the host 359 | // system's gsettings schemas and patching them to match what is expected by 360 | // the version of GTK etc shipped in the snap - this is a best effort to make 361 | // sure that the gsettings schemas are compatible between the host system and 362 | // the snap and also that we respect the host system's settings 363 | { 364 | struct override { 365 | const char *schema; 366 | const char *key; // if key is not present then the whole schema is 367 | // overridden 368 | int present; 369 | }; 370 | struct override overrides[] = { 371 | // https://github.com/alexmurray/emacs-snap/issues/101#issuecomment-2684232893 372 | {"org.gtk.Settings.FileChooser.gschema.xml", "show-type-column"}, 373 | }; 374 | int i; 375 | 376 | for (i = 0; i < sizeof(overrides) / sizeof(overrides[0]); i++) { 377 | // for each override, check if key is present in schema on host 378 | char *schema_path; 379 | FILE *schema_file; 380 | char *line = NULL; 381 | size_t len = 0; 382 | 383 | overrides[i].present = 0; 384 | 385 | asprintf(&schema_path, "/usr/share/glib-2.0/schemas/%s", 386 | overrides[i].schema); 387 | schema_file = fopen(schema_path, "r"); 388 | if (schema_file == NULL) { 389 | continue; 390 | } 391 | while (getline(&line, &len, schema_file) != -1) { 392 | if (strstr(line, overrides[i].key) != NULL) { 393 | overrides[i].present = 1; 394 | free(line); 395 | line = NULL; 396 | break; 397 | } 398 | free(line); 399 | line = NULL; 400 | } 401 | fclose(schema_file); 402 | free(schema_path); 403 | } 404 | 405 | // if any schemas are missing from the host then we need to duplicate the 406 | // hosts schemas but with the overrides from the snap for any missing ones 407 | int needed = 0; 408 | for (i = 0; i < sizeof(overrides) / sizeof(overrides[0]); i++) { 409 | needed += !overrides[i].present; 410 | } 411 | if (needed) { 412 | char *gsettings_schema_dir, *gschemas_compiled; 413 | struct stat host_st, snap_st; 414 | 415 | asprintf(&gsettings_schema_dir, "%s/.cache/schemas", snap_user_common); 416 | mkdir(gsettings_schema_dir, 0700); 417 | 418 | asprintf(&gschemas_compiled, "%s/gschemas.compiled", 419 | gsettings_schema_dir); 420 | res = stat("/usr/share/glib-2.0/schemas/gschemas.compiled", &host_st); 421 | res |= stat(gschemas_compiled, &snap_st); 422 | // if the gschemas.compiled file does not exist or the modification time 423 | // of it is older than the hosts then duplicate the host's gsettings 424 | // schemas with overrides to the snap's schemas and then compile them all 425 | if (res != 0 || host_st.st_mtime > snap_st.st_mtime) { 426 | DIR *dir; 427 | struct dirent *entry; 428 | 429 | // remove any existing symlinks etc in the cache dir 430 | dir = opendir(gsettings_schema_dir); 431 | while ((entry = readdir(dir)) != NULL) { 432 | if (entry->d_type == DT_LNK || entry->d_type == DT_REG) { 433 | char *link_path; 434 | asprintf(&link_path, "%s/%s", gsettings_schema_dir, entry->d_name); 435 | unlink(link_path); 436 | } 437 | } 438 | 439 | // duplicate the host's gsettings schemas to the snap's cache dir 440 | dir = opendir("/usr/share/glib-2.0/schemas"); 441 | while ((entry = readdir(dir)) != NULL) { 442 | if (entry->d_type == DT_REG && 443 | // only worry about .enums.xml, gschema.xml or .gschema.override 444 | // files 445 | (str_ends_with(entry->d_name, ".enums.xml") || 446 | str_ends_with(entry->d_name, ".gschema.xml") || 447 | str_ends_with(entry->d_name, ".gschema.override"))) { 448 | char *target_path, *link_path; 449 | asprintf(&target_path, "/usr/share/glib-2.0/schemas/%s", 450 | entry->d_name); 451 | asprintf(&link_path, "%s/%s", gsettings_schema_dir, entry->d_name); 452 | unlink(link_path); 453 | // symlink so we don't have to copy the schemas 454 | res = symlink(target_path, link_path); 455 | if (res < 0) { 456 | fprintf(stderr, "Failed to symlink %s to %s: %s\n", target_path, 457 | link_path, strerror(errno)); 458 | } 459 | } 460 | } 461 | // link to the schema provided by the snap for any which may be 462 | // incompatible from the host 463 | for (i = 0; i < sizeof(overrides) / sizeof(overrides[0]); i++) { 464 | char *target_path, *link_path; 465 | 466 | if (overrides[i].present) { 467 | continue; 468 | } 469 | asprintf(&target_path, "%s/usr/share/glib-2.0/schemas/%s", snap, 470 | overrides[i].schema); 471 | asprintf(&link_path, "%s/%s", gsettings_schema_dir, 472 | overrides[i].schema); 473 | unlink(link_path); 474 | res = symlink(target_path, link_path); 475 | if (res < 0) { 476 | fprintf(stderr, "Failed to symlink %s to %s: %s\n", target_path, 477 | link_path, strerror(errno)); 478 | } 479 | } 480 | 481 | // now we need to compile our frankenschema 482 | unlink(gschemas_compiled); 483 | 484 | pid_t child = fork(); 485 | if (child == -1) { 486 | fprintf(stderr, "Failed to fork: %s\n", strerror(errno)); 487 | exit(1); 488 | } 489 | if (child == 0) { 490 | // we are the child - exec glib-compile-schemas 491 | char *glib_compile_schemas; 492 | asprintf(&glib_compile_schemas, "%s/usr/bin/glib-compile-schemas", 493 | snap); 494 | res = execl(glib_compile_schemas, glib_compile_schemas, 495 | gsettings_schema_dir, NULL); 496 | if (res < 0) { 497 | fprintf(stderr, "Failed to exec %s: %s\n", glib_compile_schemas, 498 | strerror(errno)); 499 | exit(1); 500 | } 501 | } 502 | // wait for child 503 | waitpid(child, NULL, 0); 504 | 505 | // check for presence of gschemas.compiled file 506 | res = access(gschemas_compiled, F_OK); 507 | if (res < 0) { 508 | fprintf(stderr, "Failed to compile schemas: %s not found\n", 509 | gschemas_compiled); 510 | } 511 | } 512 | 513 | // set GSETTINGS_SCHEMA_DIR to the snap's cache dir 514 | setenv("GSETTINGS_SCHEMA_DIR", gsettings_schema_dir, 1); 515 | } 516 | } 517 | 518 | // create XDG_RUNTIME_DIR/gvfsd if it does not already exist to avoid 519 | // https://github.com/alexmurray/emacs-snap/issues/101 - seems the 520 | // GtkFileDialog or similar wants to monitor this directory and complains if 521 | // it doesn't exist - it may not exist since the host system is running an 522 | // older version of gvfsd-trash (which seems to own sockets within this 523 | // directory) but the snap ships a newer gtk which expects it to exist 524 | { 525 | char *xdg_runtime_dir, *gvfsd_dir; 526 | xdg_runtime_dir = getenv("XDG_RUNTIME_DIR"); 527 | if (xdg_runtime_dir) { 528 | asprintf(&gvfsd_dir, "%s/gvfsd", xdg_runtime_dir); 529 | res = mkdir(gvfsd_dir, S_IRUSR | S_IWUSR | S_IXUSR); 530 | if (res < 0 && errno != EEXIST) { 531 | fprintf(stderr, "Failed to create gvfsd dir at %s: %s\n", gvfsd_dir, 532 | strerror(errno)); 533 | } 534 | } 535 | } 536 | 537 | // finally break out of AppArmor confinement ignoring errors here since this 538 | // is best effort 539 | { 540 | int fd; 541 | char label[1024] = {0}; 542 | 543 | fd = open("/proc/self/attr/current", O_RDONLY | O_CLOEXEC); 544 | read(fd, label, sizeof(label)); 545 | close(fd); 546 | 547 | // if label starts with snap.emacs. and ends with (complain) then set to 548 | // unconfined 549 | if (strncmp(label, "snap.emacs.", strlen("snap.emacs.")) == 0 && 550 | strlen(label) > strlen("(complain)") && 551 | strncmp(label + strlen(label) - strlen("(complain)"), "(complain)", 552 | strlen("(complain)")) == 0) { 553 | // ignore errors here too 554 | fd = open("/proc/self/attr/current", O_WRONLY | O_APPEND); 555 | write(fd, "changeprofile unconfined", strlen("changeprofile unconfined")); 556 | close(fd); 557 | } 558 | } 559 | 560 | // finally exec argv if we have one 561 | if (argc > 1) { 562 | execv(argv[1], &argv[1]); 563 | } 564 | 565 | exit: 566 | exit(0); 567 | } 568 | -------------------------------------------------------------------------------- /site-lisp/site-start.el: -------------------------------------------------------------------------------- 1 | ;;; site-start.el --- Make Emacs work correctly as a snap 2 | 3 | ;;; Commentary: 4 | 5 | ;; This file contains the various settings to allow Emacs to function 6 | ;; correctly as a strictly confined snap 7 | 8 | ;;; Code: 9 | 10 | ;; since the Emacs snap is under classic confinement, it runs in the host 11 | ;; systems mount namespace and hence will use the host system's PATH etc. As 12 | ;; such, we don't want subprocesses launched by Emacs to inherit the snap 13 | ;; specific GIO_MODULE_DIR, GDK_PIXBUF and FONTCONFIG environment as they likely 14 | ;; will be linked against different libraries than what the Emacs snap base snap 15 | ;; is using. So make sure they are effectively unset. Also since we are now 16 | ;; using base core22, snapcraft helpfully sets LD_LIBRARY_PATH so unset this 17 | ;; too. 18 | (dolist (env '("GIO_MODULE_DIR" 19 | "GDK_PIXBUF_MODULE_FILE" 20 | "GDK_PIXBUF_MODULEDIR" 21 | "FONTCONFIG_FILE" 22 | "GTK_IM_MODULE_FILE" 23 | "GTK_PATH" 24 | "GSETTINGS_SCHEMA_DIR" 25 | "LD_LIBRARY_PATH")) 26 | (setenv env)) 27 | 28 | ;; keep a proxy to the SNAP env so that our patched comp.el and treesit.el can 29 | ;; use it - use /snap/emacs/current if $SNAP is not set for some reason 30 | (setenv "EMACS_SNAP_DIR" (or (getenv "SNAP") "/snap/emacs/current")) 31 | 32 | (setenv "EMACS_SNAP_USER_COMMON" (or (getenv "SNAP_USER_COMMON") (expand-file-name "~/snap/emacs/common"))) 33 | 34 | ;; ensure the correct native-comp-driver-options are set -- we also patch 35 | ;; comp.el in when building the emacs snap but do it here too to try and 36 | ;; ensure this is always set no matter what 37 | (when (require 'comp nil t) 38 | (let ((sysroot (concat (file-name-as-directory (getenv "EMACS_SNAP_USER_COMMON")) 39 | "sysroot/"))) 40 | (dolist (opt (list (concat "--sysroot=" sysroot) 41 | (concat "-B" sysroot "usr/lib/gcc/"))) 42 | (add-to-list 'native-comp-driver-options opt t)))) 43 | 44 | 45 | ;; now that we have accessed $SNAP we can unset it - and also unset *all* the 46 | ;; various SNAP environment variables so we don't confuse any other 47 | ;; applications that we launch (like say causing firefox to use the wrong 48 | ;; profile - we need to unset SNAP_NAME and SNAP_INSTANCE_NAME to stop that 49 | ;; - see https://github.com/alexmurray/emacs-snap/issues/36). 50 | (dolist (env '("SNAP_REVISION" 51 | "SNAP_REAL_HOME" 52 | "SNAP_USER_COMMON" 53 | "SNAP_INSTANCE_KEY" 54 | "SNAP_CONTEXT" 55 | "SNAP_ARCH" 56 | "SNAP_INSTANCE_NAME" 57 | "SNAP_USER_DATA" 58 | "SNAP_REEXEC" 59 | "SNAP" 60 | "SNAP_COMMON" 61 | "SNAP_VERSION" 62 | "SNAP_LIBRARY_PATH" 63 | "SNAP_COOKIE" 64 | "SNAP_DATA" 65 | "SNAP_NAME")) 66 | (setenv env)) 67 | 68 | 69 | (provide 'site-start) 70 | ;;; site-start.el ends here 71 | -------------------------------------------------------------------------------- /snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: emacs 2 | title: GNU Emacs 3 | version: "30.1" 4 | summary: GNU Emacs is the extensible self-documenting text editor 5 | description: | 6 | Emacs is the extensible, customizable, self-documenting real-time 7 | display editor. 8 | 9 | Features include: 10 | * Content-aware editing modes, including syntax coloring, for many file types. 11 | * Complete built-in documentation, including a tutorial for new users. 12 | * Full Unicode support for nearly all human scripts. 13 | * Highly customizable, using Emacs Lisp code or a graphical interface. 14 | * An entire ecosystem of functionality beyond text editing, including a project 15 | planner, mail and news reader, debugger interface, calendar, and more. 16 | * A packaging system for downloading and installing extensions. 17 | 18 | This snap is built via the build.snapcraft.io service from the 19 | snapcraft.yaml definition at https://github.com/alexmurray/emacs-snap to 20 | ensure source and build transparency. 21 | 22 | The snap contains Emacs compiled for both X11/gtk and wayland/pgtk and will 23 | automatically use the pgtk version when running under Wayland. You can 24 | override this however by setting the EMACS_TOOLKIT environment variable to 25 | either 'wayland' to specify the pgtk backend or 'gtk' to specify the X11/gtk 26 | backend. 27 | 28 | base: core24 29 | grade: stable 30 | confinement: classic 31 | license: GPL-3.0+ 32 | compression: lzo 33 | 34 | apps: 35 | emacs: 36 | command-chain: 37 | - setup-env 38 | command: emacs 39 | desktop: usr/share/applications/emacs.desktop 40 | emacsclient: 41 | command: emacsclient 42 | desktop: usr/share/applications/emacsclient.desktop 43 | ctags: 44 | command: usr/bin/ctags 45 | ebrowse: 46 | command: usr/bin/ebrowse 47 | etags: 48 | command: usr/bin/etags 49 | 50 | parts: 51 | tree-sitter: 52 | plugin: make 53 | build-attributes: 54 | - enable-patchelf 55 | source: https://github.com/tree-sitter/tree-sitter.git 56 | source-tag: v0.25.6 57 | build-environment: 58 | - PREFIX: "/usr" 59 | emacs: 60 | after: [tree-sitter] 61 | plugin: nil 62 | build-attributes: 63 | - enable-patchelf 64 | source: https://mirrors.ocf.berkeley.edu/gnu/emacs/emacs-30.1.tar.xz 65 | source-checksum: sha256/6ccac1ae76e6af93c6de1df175e8eb406767c23da3dd2a16aa67e3124a6f138f 66 | organize: 67 | snap/emacs/current/usr: usr 68 | build-packages: 69 | - autoconf 70 | - automake 71 | - autopoint 72 | - bsd-mailx 73 | - dbus-x11 74 | - debhelper 75 | - dpkg-dev 76 | - gawk 77 | - gcc-14 78 | - g++-14 79 | - libacl1-dev 80 | - libasound2-dev 81 | - libdbus-1-dev 82 | - libgccjit-14-dev 83 | - libgif-dev 84 | - libgnutls28-dev 85 | - libgpm-dev 86 | - libgtk-3-dev 87 | - libharfbuzz-dev 88 | - libjpeg-dev 89 | - liblcms2-dev 90 | - liblockfile-dev 91 | - libm17n-dev 92 | - libncurses-dev 93 | - liboss4-salsa2 94 | - libotf-dev 95 | - libpng-dev 96 | - librsvg2-dev 97 | - libselinux1-dev 98 | - libsqlite3-dev 99 | - libsystemd-dev 100 | - libtiff-dev 101 | - libtool 102 | - libxi-dev 103 | - libxml2-dev 104 | - libxpm-dev 105 | - libxt-dev 106 | - procps 107 | - quilt 108 | - sharutils 109 | - texinfo 110 | - zlib1g-dev 111 | stage-packages: 112 | - gsettings-desktop-schemas 113 | - gcc-14 # for tree-sitter 114 | - g++-14 # for tree-sitter 115 | - gvfs 116 | - gvfs-libs 117 | - ibus-gtk3 118 | - libacl1 119 | - libattr1 120 | - libasound2t64 121 | - libaspell15 122 | - libasyncns0 123 | - libatk-bridge2.0-0t64 124 | - libatk1.0-0t64 125 | - libatspi2.0-0t64 126 | - libblkid1 127 | - libbrotli1 128 | - libbsd0 129 | - libc6 # required for native-comp 130 | - libc6-dev # required for native-comp 131 | - libcairo-gobject2 132 | - libcairo2 133 | - libcanberra-gtk3-0t64 134 | - libcanberra-gtk3-module # to avoid startup error: Failed to load module "canberra-gtk-module" 135 | - libcanberra0t64 136 | - libdatrie1 137 | - libdb5.3t64 138 | - libdrm2 139 | - libdevmapper1.02.1 140 | - libegl1 141 | - libenchant-2-2 142 | - libenchant-2-dev # for https://github.com/alexmurray/emacs-snap/issues/71 143 | - libepoxy0 144 | - libexpat1 145 | - libffi8 146 | - libflac12t64 147 | - libfontconfig1 148 | - libfreetype6 149 | - libgbm1 150 | - libgccjit0 151 | - libgccjit-14-dev 152 | - libgcc-s1 153 | - libgcrypt20 154 | - libgd3 155 | - libgdk-pixbuf2.0-0 156 | - libgif7 157 | - libgl1 158 | - libglib2.0-0t64 159 | - libglib2.0-bin 160 | - libglvnd0 161 | - libglx0 162 | - libgmp10 163 | - libgnutls30t64 164 | - libhogweed6t64 165 | - libidn2-0 166 | - liblz4-1 167 | - libmd0 168 | - libnettle8t64 169 | - libsystemd0 170 | - libtasn1-6 171 | - libunistring5 172 | - libzstd1 173 | - libgpg-error0 174 | - libgpm2 175 | - libgraphite2-3 176 | - libgstreamer-gl1.0-0 177 | - libgstreamer-plugins-base1.0-0 178 | - libgstreamer1.0-0 179 | - libgtk-3-0t64 180 | - libgudev-1.0-0 181 | - libharfbuzz-icu0 182 | - libharfbuzz0b 183 | - libhyphen0 184 | - libibus-1.0-5 185 | - libice6 186 | - libicu74 187 | - libisl23 188 | - libjbig0 189 | - libjpeg-turbo8 190 | - libcom-err2 191 | - liblcms2-2 192 | - liblockfile1 193 | - libltdl7 194 | - libm17n-0 195 | - libmount1 196 | - libmpc3 197 | - libmpfr6 198 | - libnotify4 199 | - libnss-mdns 200 | - libnss-myhostname 201 | - libnss-sss 202 | - libnss-systemd 203 | - libogg0 204 | - libedit2 205 | - libelf1t64 206 | - libreadline8t64 207 | - libncursesw6 208 | - libkeyutils1 209 | - libtinfo6 210 | - libbz2-1.0 211 | - libdbus-1-3 212 | - liblzma5 213 | - libcap2 214 | - liborc-0.4-0t64 215 | - liboss4-salsa2 216 | - libotf1 217 | - libp11-kit0 218 | - libpango-1.0-0 219 | - libpangocairo-1.0-0 220 | - libpangoft2-1.0-0 221 | - libpixman-1-0 222 | - libpng16-16t64 223 | - libpulse0 224 | - librsvg2-2 225 | - libsasl2-2 226 | - libsecret-1-0 227 | - libselinux1 228 | - libsm6 229 | - libsndfile1 230 | - libsoup2.4-common 231 | - libsqlite3-0 232 | - libssl3t64 233 | - libsss-nss-idmap0 234 | - libstdc++6 235 | - libtdb1 236 | - libthai0 237 | - libtiff6 238 | - libuuid1 239 | - libvorbis0a 240 | - libvorbisenc2 241 | - libvorbisfile3 242 | - libwayland-client0 243 | - libwayland-cursor0 244 | - libwayland-egl1 245 | - libwayland-server0 246 | - libwebp7 247 | - libwebpdecoder3 248 | - libwebpdemux2 249 | - libwoff1 250 | - libxml2 251 | - libxpm4 252 | - libxslt1.1 253 | - libyajl2 254 | - libx11-6 255 | - libx11-xcb1 256 | - libxau6 257 | - libxapian30 258 | - libxapian-dev # https://github.com/alexmurray/emacs-snap/issues/92 259 | - libxcb-render0 260 | - libxcb-shm0 261 | - libxcb1 262 | - libxcomposite1 263 | - libxcursor1 264 | - libxdamage1 265 | - libxdmcp6 266 | - libxext6 267 | - libxfixes3 268 | - libxi6 269 | - libxinerama1 270 | - libxkbcommon0 271 | - libxrandr2 272 | - libxrender1 273 | - packagekit-gtk3-module # to avoid startup error: Failed to load module "pk-gtk-module" 274 | - zlib1g 275 | stage: 276 | - -usr/share/emacs/site-lisp 277 | build-environment: 278 | - CC: "gcc-14" 279 | - CXX: "g++-14" 280 | - CFLAGS: "${CFLAGS:+$CFLAGS} -O2" 281 | - NATIVE_FULL_AOT: "1" 282 | - LD_LIBRARY_PATH: "$SNAPCRAFT_STAGE/usr/lib" 283 | override-pull: | 284 | craftctl default 285 | # ensure we hard-code our copy of gcc-14 and g++14 for tree-sitter 286 | # otherwise it will use the system installed ones which will have a 287 | # different libc version and we will fail to load them 288 | patch -p1 < $SNAPCRAFT_PROJECT_DIR/treesit.patch 289 | 290 | # also hard-code the default x resource name to emacs rather than using 291 | # the invocation name (which is emacs-gtk or similar) 292 | patch -p1 < $SNAPCRAFT_PROJECT_DIR/emacs-x-resource-name.patch 293 | 294 | override-build: | 295 | # build without pgtk for x11 first 296 | env NOCONFIGURE=1 ./autogen.sh 297 | ./configure \ 298 | --prefix=/snap/emacs/current/usr \ 299 | --with-x-toolkit=gtk3 \ 300 | --without-xaw3d \ 301 | --with-modules \ 302 | --with-cairo \ 303 | --with-native-compilation=aot \ 304 | --without-pgtk \ 305 | --with-xinput2 \ 306 | --with-tree-sitter 307 | env NATIVE_FULL_AOT=1 make -j"${SNAPCRAFT_PARALLEL_BUILD_COUNT}" 308 | cp src/emacs src/emacs-gtk 309 | cp lib-src/emacsclient lib-src/emacsclient-gtk 310 | cp src/emacs.pdmp src/emacs-gtk.pdmp 311 | 312 | # then build with pgtk as well 313 | make maintainer-clean 314 | env NOCONFIGURE=1 ./autogen.sh 315 | ./configure \ 316 | --prefix=/snap/emacs/current/usr \ 317 | --with-x-toolkit=gtk3 \ 318 | --without-xaw3d \ 319 | --with-modules \ 320 | --with-cairo \ 321 | --with-native-compilation=aot \ 322 | --with-pgtk \ 323 | --with-xinput2 \ 324 | --with-tree-sitter 325 | env NATIVE_FULL_AOT=1 make -j"${SNAPCRAFT_PARALLEL_BUILD_COUNT}" 326 | cp src/emacs src/emacs-wayland 327 | cp lib-src/emacsclient lib-src/emacsclient-wayland 328 | cp src/emacs.pdmp src/emacs-wayland.pdmp 329 | 330 | make install DESTDIR="${SNAPCRAFT_PART_INSTALL}" 331 | 332 | for backend in gtk wayland; do 333 | install -m 0755 src/emacs-$backend $SNAPCRAFT_PART_INSTALL/usr/bin/ 334 | install -m 0755 lib-src/emacsclient-$backend $SNAPCRAFT_PART_INSTALL/usr/bin/ 335 | # cheat by using the already installed pdmp file from above as a template 336 | pdmp_dir=$(dirname $(ls -1 $SNAPCRAFT_PART_INSTALL/snap/emacs/current/usr/libexec/emacs/*/*/emacs-*.pdmp | head -n1)) 337 | install -m 0644 "src/emacs-$backend.pdmp" "${pdmp_dir}" 338 | ln -sf "emacs-$backend.pdmp" "${pdmp_dir}/emacs-$backend-$(src/emacs-$backend --fingerprint).pdmp" 339 | done 340 | 341 | glib-compile-schemas $SNAPCRAFT_PART_INSTALL/usr/share/glib-2.0/schemas/ 342 | 343 | override-stage: | 344 | set -eu 345 | craftctl default 346 | # Fix-up application icon lookup 347 | sed -i -e 's|^Icon=.*|Icon=usr/share/icons/hicolor/scalable/apps/emacs.svg|g' usr/share/applications/emacs*.desktop 348 | # also fixup emacsclient.desktop file since snapd replaces the Exec= line 349 | sed -i 's|Exec=.*|Exec=emacsclient %F|' usr/share/applications/emacsclient.desktop 350 | # ensure we hard-code the necessary arguments for 351 | # native-comp-driver-options in the build to try and avoid 352 | # https://github.com/alexmurray/emacs-snap/issues/35 - we can't do 353 | # this before we compile otherwise these options get used for doing 354 | # the AOT compile as well - so set them afterwards just inside the 355 | # snap 356 | VERSION=$(ls -1 usr/share/emacs/*/lisp/emacs-lisp/comp.el* | head -n1 | cut -d / -f 4) 357 | rm -f "usr/share/emacs/${VERSION}/lisp/emacs-lisp/comp.elc" 358 | [ -f "usr/share/emacs/${VERSION}/lisp/emacs-lisp/comp.el.gz" ] && gunzip -f usr/share/emacs/*/lisp/emacs-lisp/comp.el.gz 359 | # check if patched first so we can run prime stage multiple times when 360 | # iterating on changes 361 | grep -q SNAP "usr/share/emacs/${VERSION}/lisp/emacs-lisp/comp.el" || sed "s/@@VERSION@@/${VERSION}/g" $SNAPCRAFT_PROJECT_DIR/native-comp.patch | sed "s/@@MULTIARCH@@/$CRAFT_ARCH_TRIPLET_BUILD_FOR/g" | patch -p1 362 | ../parts/emacs/build/src/emacs -batch -f batch-byte+native-compile "usr/share/emacs/${VERSION}/lisp/emacs-lisp/comp.el" 363 | # compiled eln files get put back in the build directory - copy them over 364 | # manually - there may be more than one directory here but only one will 365 | # contain the comp related eln files 366 | VERSION_HASH=$(basename $(dirname ../parts/emacs/build/native-lisp/*/comp-common*.eln)) 367 | cp "../parts/emacs/build/native-lisp/${VERSION_HASH}"/comp-*.eln "usr/lib/emacs/${VERSION}/native-lisp/${VERSION_HASH}/" 368 | gzip usr/share/emacs/*/lisp/emacs-lisp/comp.el 369 | 370 | # fix up paths in the various pkg-config files that we ship 371 | find $SNAPCRAFT_STAGE -name *.pc -exec sed -i "s|prefix=$SNAPCRAFT_STAGE|prefix=/snap/emacs/current|" {} \; 372 | 373 | # finally fixup linker script to use relative path for libc.so.6 and add 374 | # multiple search paths to it to fix 375 | # https://github.com/alexmurray/emacs-snap/issues/96 and use our 376 | # libc_nonshared.a 377 | sed -i "s| /lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/| |" usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libc.so 378 | sed -i "s| /usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libc_nonshared.a| /snap/emacs/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libc_nonshared.a|" usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libc.so 379 | echo 'SEARCH_DIR( /lib/x86_64-linux-gnu )' >> usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libc.so 380 | echo 'SEARCH_DIR( /lib64/ )' >> usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libc.so 381 | 382 | site-lisp: 383 | source: site-lisp 384 | plugin: dump 385 | organize: 386 | "*.el": usr/share/emacs/site-lisp/ 387 | setup-env: 388 | source: setup-env 389 | plugin: make 390 | build-attributes: 391 | - enable-patchelf 392 | stage: 393 | - setup-env 394 | scripts: 395 | source: . 396 | plugin: dump 397 | stage: 398 | - emacsclient 399 | - emacs 400 | -------------------------------------------------------------------------------- /spread.yaml: -------------------------------------------------------------------------------- 1 | project: emacs-snap 2 | 3 | backends: 4 | qemu: 5 | systems: 6 | # build via autopkgtest-buildvm-ubuntu-cloud and mv to ~/.spread/qemu/ 7 | - ubuntu-18.04-64: 8 | username: ubuntu 9 | password: ubuntu 10 | - ubuntu-20.04-64: 11 | username: ubuntu 12 | password: ubuntu 13 | - ubuntu-22.04-64: 14 | username: ubuntu 15 | password: ubuntu 16 | - ubuntu-24.04-64: 17 | username: ubuntu 18 | password: ubuntu 19 | 20 | # build an image as per 21 | # https://blog.strandboge.com/2019/04/16/cloud-images-qemu-cloud-init-and-snapd-spread-tests/ 22 | - centos-8-64: 23 | username: centos 24 | password: centos 25 | # also build as per jdstrand's blog post but using image from 26 | # https://dl.rockylinux.org/pub/rocky/8/images/x86_64/ 27 | # wget https://dl.rockylinux.org/pub/rocky/8/images/x86_64/Rocky-8-GenericCloud.latest.x86_64.qcow2 28 | # #cp instead of mv so we can try again if we mess it up 29 | # cp Rocky-8-GenericCloud.latest.x86_64.qcow2 rockylinux-8-64.img 30 | # qemu-img resize rockylinux-8-64.img 20G 31 | # cat <rockylinux-data 32 | # #cloud-config 33 | # password: rocky 34 | # chpasswd: { expire: false } 35 | # ssh_pwauth: true 36 | # EOF 37 | # cat <rockylinux-meta-data 38 | # instance-id: i-rockylinux-8-64 39 | # local-hostname: rockylinux-8-64 40 | # EOF 41 | # cloud-localds -v ./rockylinux-seed.img ./rockylinux-data ./rockylinux-meta-data 42 | # kvm -M pc -m 1024 -smp 1 -monitor pty -nographic -hda ./rockylinux-8-64.img -drive "file=./rockylinux-seed.img,if=virtio,format=raw" -net nic -net user,hostfwd=tcp:127.0.0.1:59355-:22 43 | # 44 | # wait for it to boot and finish configuring via cloud-init then in another terminal 45 | # ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -p 59355 rocky@127.0.0.1 sudo touch /etc/cloud/cloud-init.disabled 46 | # ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -p 59355 rocky@127.0.0.1 sudo shutdown -h now 47 | # mv rockylinux-8-64.img ~/.spread/qemu/ 48 | - rockylinux-8-64: 49 | username: rocky 50 | password: rocky 51 | 52 | path: /spread/emacs-snap 53 | 54 | exclude: 55 | - .git 56 | - .github 57 | - emacs*.snap 58 | 59 | # make sure we don't get killed during prepare 60 | kill-timeout: 30m 61 | 62 | environment: 63 | CHANNEL/latest_stable: latest/stable 64 | CHANNEL/latest_candidate: latest/candidate 65 | CHANNEL/latest_beta: latest/beta 66 | CHANNEL/latest_edge: latest/edge 67 | 68 | suites: 69 | tests/: 70 | summary: spread tests 71 | prepare: | 72 | case $SPREAD_SYSTEM in 73 | centos-*|rockylinux-*) 74 | if [ $SPREAD_REBOOT = 0 ]; then 75 | # disable SELinux since otherwise snapd can't seem to talk to store 76 | sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config 77 | REBOOT 78 | fi 79 | dnf update -y 80 | dnf install -y epel-release 81 | dnf upgrade -y 82 | dnf install -y selinux-policy selinux-policy-targeted 83 | dnf install -y snapd git gcc 84 | systemctl enable --now snapd.socket 85 | ln -sf /var/lib/snapd/snap /snap 86 | snap wait system seed.loaded 87 | ;; 88 | ubuntu-*) 89 | # ensure machine is up to date 90 | export DEBIAN_FRONTEND=noninteractive 91 | # use apt-get to support older Ubuntu releases 92 | apt-get update 93 | apt-get -o Dpkg::Options::="--force-confold" -o Dpkg::Options::="--force-confdef" upgrade -y 94 | # use apt-get to support older Ubuntu releases 95 | apt-get autoremove --purge -y 96 | apt install -y snapd git gcc 97 | ;; 98 | *) 99 | echo "ERROR: Unsupported system: $SPREAD_SYSTEM" 100 | exit 1 101 | ;; 102 | esac 103 | 104 | prepare-each: | 105 | snap install emacs --classic --channel $CHANNEL 106 | 107 | restore-each: | 108 | snap remove emacs 109 | rm -rf snap/emacs 110 | rm -rf .emacs.d 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /tests/native-comp/task.yaml: -------------------------------------------------------------------------------- 1 | summary: Test native compilation 2 | 3 | prepare: | 4 | echo '(defun foo () (message "foo"))' >> test.el 5 | 6 | restore: | 7 | rm -f test.el 8 | 9 | execute: | 10 | # test native compilation 11 | emacs --batch --eval '(native-compile "test.el")' 12 | -------------------------------------------------------------------------------- /tests/setup-env/task.yaml: -------------------------------------------------------------------------------- 1 | summary: Test setup-env 2 | 3 | prepare: | 4 | # make sure we have some schemas to work with 5 | if [ ! -f /usr/share/glib-2.0/schemas/gschemas.compiled ]; then 6 | cp /snap/emacs/current/usr/share/glib-2.0/schemas/*.xml /usr/share/glib-2.0/schemas 7 | fi 8 | if [ -f /usr/share/glib-2.0/schemas/org.gtk.Settings.FileChooser.gschema.xml ]; then 9 | mv /usr/share/glib-2.0/schemas/org.gtk.Settings.FileChooser.gschema.xml /usr/share/glib-2.0/schemas/org.gtk.Settings.FileChooser.gschema.xml.bak 10 | fi 11 | 12 | restore: | 13 | if [ -f /usr/share/glib-2.0/schemas/org.gtk.Settings.FileChooser.gschema.xml.bak ]; then 14 | mv /usr/share/glib-2.0/schemas/org.gtk.Settings.FileChooser.gschema.xml.bak /usr/share/glib-2.0/schemas/org.gtk.Settings.FileChooser.gschema.xml 15 | fi 16 | if [ ! -f /usr/share/glib-2.0/schemas/gschemas.compiled ]; then 17 | rm -f /usr/share/glib-2.0/schemas/*.xml 18 | fi 19 | 20 | execute: | 21 | ! test -d ~/snap/emacs/common/.cache/schemas 22 | 23 | # test gschemas handling 24 | emacs --batch --eval '(kill-emacs)' 25 | 26 | test -d ~/snap/emacs/common/.cache/schemas 27 | test -f ~/snap/emacs/common/.cache/schemas/gschemas.compiled 28 | # use realpath to check for equivalence since on other platforms /snap is a 29 | # symlink to /var/lib/snapd/snap 30 | [[ $(realpath ~/snap/emacs/common/.cache/schemas/org.gtk.Settings.FileChooser.gschema.xml) == $(realpath /snap/emacs/current/usr/share/glib-2.0/schemas/org.gtk.Settings.FileChooser.gschema.xml) ]] 31 | -------------------------------------------------------------------------------- /tests/treesit/task.yaml: -------------------------------------------------------------------------------- 1 | summary: Test treesitter module compilation 2 | 3 | execute: | 4 | # test treesit compilation 5 | emacs --batch --eval "(progn (require 'treesit) (add-to-list 'treesit-language-source-alist '(c . (\"https://github.com/tree-sitter/tree-sitter-c\"))) (treesit-install-language-grammar 'c))" 6 | test -f ~/.emacs.d/tree-sitter/libtree-sitter-c.so 7 | -------------------------------------------------------------------------------- /treesit.patch: -------------------------------------------------------------------------------- 1 | diff --git a/lisp/treesit.el b/lisp/treesit.el 2 | index 2518204ce93..7e6aef76bd1 100644 3 | --- a/lisp/treesit.el 4 | +++ b/lisp/treesit.el 5 | @@ -3790,17 +3790,19 @@ function signals an error." 6 | maybe-repo-dir 7 | (expand-file-name "repo"))) 8 | (source-dir (expand-file-name (or source-dir "src") workdir)) 9 | - (cc (or cc (seq-find #'executable-find '("cc" "gcc" "c99")) 10 | + (cc (or cc (concat (file-name-as-directory (getenv "EMACS_SNAP_DIR")) "usr/bin/gcc-14") 11 | ;; If no C compiler found, just use cc and let 12 | ;; `call-process' signal the error. 13 | "cc")) 14 | - (c++ (or c++ (seq-find #'executable-find '("c++" "g++")) 15 | + (c++ (or c++ (concat (file-name-as-directory (getenv "EMACS_SNAP_DIR")) "usr/bin/g++-14") 16 | "c++")) 17 | (soext (or (car dynamic-library-suffixes) 18 | (signal 'treesit-error '("Emacs cannot figure out the file extension for dynamic libraries for this system, because `dynamic-library-suffixes' is nil")))) 19 | (out-dir (or (and out-dir (expand-file-name out-dir)) 20 | (locate-user-emacs-file "tree-sitter"))) 21 | - (lib-name (concat "libtree-sitter-" lang soext))) 22 | + (lib-name (concat "libtree-sitter-" lang soext)) 23 | + (process-environment (copy-sequence process-environment))) 24 | + (setenv "COMPILER_PATH" (concat (file-name-as-directory (getenv "EMACS_SNAP_DIR")) "usr/bin")) 25 | (unwind-protect 26 | (with-temp-buffer 27 | (if url-is-dir 28 | @@ -3814,15 +3816,24 @@ function signals an error." 29 | (message "Compiling library") 30 | ;; cc -fPIC -c -I. parser.c 31 | (treesit--call-process-signal 32 | - cc nil t nil "-fPIC" "-c" "-I." "parser.c") 33 | + cc nil t nil "-fPIC" "-c" "-I." 34 | + "--sysroot" (concat (file-name-as-directory (getenv "EMACS_SNAP_USER_COMMON")) "sysroot/") 35 | + "-B" (concat (file-name-as-directory (getenv "EMACS_SNAP_USER_COMMON")) "sysroot/usr/lib/gcc") 36 | + "parser.c") 37 | ;; cc -fPIC -c -I. scanner.c 38 | (when (file-exists-p "scanner.c") 39 | (treesit--call-process-signal 40 | - cc nil t nil "-fPIC" "-c" "-I." "scanner.c")) 41 | + cc nil t nil "-fPIC" "-c" "-I." 42 | + "--sysroot" (concat (file-name-as-directory (getenv "EMACS_SNAP_USER_COMMON")) "sysroot/") 43 | + "-B" (concat (file-name-as-directory (getenv "EMACS_SNAP_USER_COMMON")) "sysroot/usr/lib/gcc") 44 | + "scanner.c")) 45 | ;; c++ -fPIC -I. -c scanner.cc 46 | (when (file-exists-p "scanner.cc") 47 | (treesit--call-process-signal 48 | - c++ nil t nil "-fPIC" "-c" "-I." "scanner.cc")) 49 | + c++ nil t nil "-fPIC" "-c" "-I." 50 | + "--sysroot" (concat (file-name-as-directory (getenv "EMACS_SNAP_USER_COMMON")) "sysroot/") 51 | + "-B" (concat (file-name-as-directory (getenv "EMACS_SNAP_USER_COMMON")) "sysroot/usr/lib/gcc") 52 | + "scanner.cc")) 53 | ;; cc/c++ -fPIC -shared *.o -o "libtree-sitter-${lang}.${soext}" 54 | (apply #'treesit--call-process-signal 55 | (if (file-exists-p "scanner.cc") c++ cc) 56 | @@ -3834,6 +3845,8 @@ function signals an error." 57 | (rx bos (+ anychar) ".o" eos)) 58 | "-o" ,lib-name) 59 | `("-fPIC" "-shared" 60 | + "--sysroot" ,(concat (file-name-as-directory (getenv "EMACS_SNAP_USER_COMMON")) "sysroot/") 61 | + "-B" ,(concat (file-name-as-directory (getenv "EMACS_SNAP_USER_COMMON")) "sysroot/usr/lib/gcc") 62 | ,@(directory-files 63 | default-directory nil 64 | (rx bos (+ anychar) ".o" eos)) 65 | --------------------------------------------------------------------------------